sinatra-base 1.0 → 1.4.0
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.
- data/.yardopts +4 -0
- data/AUTHORS +15 -0
- data/CHANGES +524 -1
- data/Gemfile +82 -0
- data/LICENSE +1 -1
- data/README.de.rdoc +2093 -0
- data/README.es.rdoc +2091 -0
- data/README.fr.rdoc +2116 -0
- data/README.hu.rdoc +607 -0
- data/README.jp.rdoc +514 -23
- data/README.pt-br.rdoc +647 -0
- data/README.pt-pt.rdoc +646 -0
- data/README.rdoc +1580 -205
- data/README.ru.rdoc +2015 -0
- data/README.zh.rdoc +1816 -0
- data/Rakefile +110 -44
- data/examples/chat.rb +61 -0
- data/examples/simple.rb +3 -0
- data/examples/stream.ru +26 -0
- data/lib/sinatra.rb +0 -3
- data/lib/sinatra/base.rb +923 -393
- data/lib/sinatra/main.rb +9 -7
- data/lib/sinatra/showexceptions.rb +37 -4
- data/lib/sinatra/version.rb +3 -0
- data/sinatra-base.gemspec +15 -91
- data/test/base_test.rb +2 -2
- data/test/builder_test.rb +32 -2
- data/test/coffee_test.rb +92 -0
- data/test/contest.rb +62 -28
- data/test/creole_test.rb +65 -0
- data/test/delegator_test.rb +162 -0
- data/test/encoding_test.rb +20 -0
- data/test/erb_test.rb +25 -2
- data/test/extensions_test.rb +1 -1
- data/test/filter_test.rb +226 -8
- data/test/haml_test.rb +8 -2
- data/test/helper.rb +47 -0
- data/test/helpers_test.rb +1287 -80
- data/test/integration/app.rb +62 -0
- data/test/integration_helper.rb +208 -0
- data/test/integration_test.rb +82 -0
- data/test/less_test.rb +36 -6
- data/test/liquid_test.rb +59 -0
- data/test/mapped_error_test.rb +84 -7
- data/test/markaby_test.rb +80 -0
- data/test/markdown_test.rb +81 -0
- data/test/middleware_test.rb +1 -1
- data/test/nokogiri_test.rb +69 -0
- data/test/rack_test.rb +45 -0
- data/test/radius_test.rb +59 -0
- data/test/rdoc_test.rb +66 -0
- data/test/readme_test.rb +136 -0
- data/test/request_test.rb +13 -1
- data/test/response_test.rb +21 -2
- data/test/result_test.rb +5 -5
- data/test/route_added_hook_test.rb +1 -1
- data/test/routing_test.rb +328 -13
- data/test/sass_test.rb +48 -18
- data/test/scss_test.rb +88 -0
- data/test/server_test.rb +4 -3
- data/test/settings_test.rb +191 -21
- data/test/sinatra_test.rb +5 -1
- data/test/slim_test.rb +88 -0
- data/test/static_test.rb +89 -5
- data/test/streaming_test.rb +140 -0
- data/test/templates_test.rb +143 -4
- data/test/textile_test.rb +65 -0
- data/test/views/a/in_a.str +1 -0
- data/test/views/ascii.erb +2 -0
- data/test/views/b/in_b.str +1 -0
- data/test/views/calc.html.erb +1 -0
- data/test/views/explicitly_nested.str +1 -0
- data/test/views/hello.coffee +1 -0
- data/test/views/hello.creole +1 -0
- data/test/views/hello.liquid +1 -0
- data/test/views/hello.mab +1 -0
- data/test/views/hello.md +1 -0
- data/test/views/hello.nokogiri +1 -0
- data/test/views/hello.radius +1 -0
- data/test/views/hello.rdoc +1 -0
- data/test/views/hello.sass +1 -1
- data/test/views/hello.scss +3 -0
- data/test/views/hello.slim +1 -0
- data/test/views/hello.str +1 -0
- data/test/views/hello.textile +1 -0
- data/test/views/hello.yajl +1 -0
- data/test/views/layout2.liquid +2 -0
- data/test/views/layout2.mab +2 -0
- data/test/views/layout2.nokogiri +3 -0
- data/test/views/layout2.radius +2 -0
- data/test/views/layout2.slim +3 -0
- data/test/views/layout2.str +2 -0
- data/test/views/nested.str +1 -0
- data/test/views/utf8.erb +2 -0
- data/test/yajl_test.rb +80 -0
- metadata +126 -91
- data/lib/sinatra/tilt.rb +0 -746
- data/test/erubis_test.rb +0 -82
- data/test/views/error.erubis +0 -3
- data/test/views/hello.erubis +0 -1
- data/test/views/layout2.erubis +0 -2
data/Rakefile
CHANGED
@@ -1,58 +1,130 @@
|
|
1
1
|
require 'rake/clean'
|
2
2
|
require 'rake/testtask'
|
3
3
|
require 'fileutils'
|
4
|
+
require 'date'
|
5
|
+
|
6
|
+
# CI Reporter is only needed for the CI
|
7
|
+
begin
|
8
|
+
require 'ci/reporter/rake/test_unit'
|
9
|
+
rescue LoadError
|
10
|
+
end
|
4
11
|
|
5
12
|
task :default => :test
|
6
13
|
task :spec => :test
|
7
14
|
|
15
|
+
CLEAN.include "**/*.rbc"
|
16
|
+
|
8
17
|
def source_version
|
9
|
-
|
10
|
-
|
18
|
+
@source_version ||= begin
|
19
|
+
load './lib/sinatra/version.rb'
|
20
|
+
Sinatra::VERSION
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def prev_feature
|
25
|
+
source_version.gsub(/^(\d\.)(\d+)\..*$/) { $1 + ($2.to_i - 1).to_s }
|
26
|
+
end
|
27
|
+
|
28
|
+
def prev_version
|
29
|
+
return prev_feature + '.0' if source_version.end_with? '.0'
|
30
|
+
source_version.gsub(/\d+$/) { |s| s.to_i - 1 }
|
11
31
|
end
|
12
32
|
|
13
33
|
# SPECS ===============================================================
|
14
34
|
|
35
|
+
task :test do
|
36
|
+
ENV['LANG'] = 'C'
|
37
|
+
ENV.delete 'LC_CTYPE'
|
38
|
+
end
|
39
|
+
|
15
40
|
Rake::TestTask.new(:test) do |t|
|
16
41
|
t.test_files = FileList['test/*_test.rb']
|
17
|
-
t.ruby_opts = ['-rubygems
|
42
|
+
t.ruby_opts = ['-rubygems'] if defined? Gem
|
43
|
+
t.ruby_opts << '-I.'
|
44
|
+
end
|
45
|
+
|
46
|
+
Rake::TestTask.new(:"test:core") do |t|
|
47
|
+
core_tests = %w[base delegator encoding extensions filter
|
48
|
+
helpers mapped_error middleware radius rdoc
|
49
|
+
readme request response result route_added_hook
|
50
|
+
routing server settings sinatra static templates]
|
51
|
+
t.test_files = core_tests.map {|n| "test/#{n}_test.rb"}
|
52
|
+
t.ruby_opts = ["-rubygems"] if defined? Gem
|
53
|
+
t.ruby_opts << "-I."
|
18
54
|
end
|
19
55
|
|
20
56
|
# Rcov ================================================================
|
57
|
+
|
21
58
|
namespace :test do
|
22
59
|
desc 'Mesures test coverage'
|
23
60
|
task :coverage do
|
24
61
|
rm_f "coverage"
|
25
|
-
|
26
|
-
system("#{rcov} --no-html --no-color test/*_test.rb")
|
62
|
+
sh "rcov -Ilib test/*_test.rb"
|
27
63
|
end
|
28
64
|
end
|
29
65
|
|
30
66
|
# Website =============================================================
|
31
|
-
# Building docs requires HAML and the hanna gem:
|
32
|
-
# gem install mislav-hanna --source=http://gems.github.com
|
33
67
|
|
34
68
|
desc 'Generate RDoc under doc/api'
|
35
69
|
task 'doc' => ['doc:api']
|
70
|
+
task('doc:api') { sh "yardoc -o doc/api" }
|
71
|
+
CLEAN.include 'doc/api'
|
36
72
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
73
|
+
# README ===============================================================
|
74
|
+
|
75
|
+
task :add_template, [:name] do |t, args|
|
76
|
+
Dir.glob('README.*') do |file|
|
77
|
+
code = File.read(file)
|
78
|
+
if code =~ /^===.*#{args.name.capitalize}/
|
79
|
+
puts "Already covered in #{file}"
|
80
|
+
else
|
81
|
+
template = code[/===[^\n]*Liquid.*index\.liquid<\/tt>[^\n]*/m]
|
82
|
+
if !template
|
83
|
+
puts "Liquid not found in #{file}"
|
84
|
+
else
|
85
|
+
puts "Adding section to #{file}"
|
86
|
+
template = template.gsub(/Liquid/, args.name.capitalize).gsub(/liquid/, args.name.downcase)
|
87
|
+
code.gsub! /^(\s*===.*CoffeeScript)/, "\n" << template << "\n\\1"
|
88
|
+
File.open(file, "w") { |f| f << code }
|
89
|
+
end
|
90
|
+
end
|
53
91
|
end
|
54
92
|
end
|
55
|
-
|
93
|
+
|
94
|
+
# Thanks in announcement ===============================================
|
95
|
+
|
96
|
+
team = ["Ryan Tomayko", "Blake Mizerany", "Simon Rozet", "Konstantin Haase"]
|
97
|
+
desc "list of contributors"
|
98
|
+
task :thanks, [:release,:backports] do |t, a|
|
99
|
+
a.with_defaults :release => "#{prev_version}..HEAD",
|
100
|
+
:backports => "#{prev_feature}.0..#{prev_feature}.x"
|
101
|
+
included = `git log --format=format:"%aN\t%s" #{a.release}`.lines.to_a
|
102
|
+
excluded = `git log --format=format:"%aN\t%s" #{a.backports}`.lines.to_a
|
103
|
+
commits = (included - excluded).group_by { |c| c[/^[^\t]+/] }
|
104
|
+
authors = commits.keys.sort_by { |n| - commits[n].size } - team
|
105
|
+
puts authors[0..-2].join(', ') << " and " << authors.last,
|
106
|
+
"(based on commits included in #{a.release}, but not in #{a.backports})"
|
107
|
+
end
|
108
|
+
|
109
|
+
desc "list of authors"
|
110
|
+
task :authors, [:commit_range, :format, :sep] do |t, a|
|
111
|
+
a.with_defaults :format => "%s (%d)", :sep => ", ", :commit_range => '--all'
|
112
|
+
authors = Hash.new { |h,k| h[k] = 0 }
|
113
|
+
blake = "Blake Mizerany"
|
114
|
+
overall = 0
|
115
|
+
mapping = {
|
116
|
+
"blake.mizerany@gmail.com" => blake, "bmizerany" => blake,
|
117
|
+
"a_user@mac.com" => blake, "ichverstehe" => "Harry Vangberg",
|
118
|
+
"Wu Jiang (nouse)" => "Wu Jiang" }
|
119
|
+
`git shortlog -s #{a.commit_range}`.lines.map do |line|
|
120
|
+
line = line.force_encoding 'binary' if line.respond_to? :force_encoding
|
121
|
+
num, name = line.split("\t", 2).map(&:strip)
|
122
|
+
authors[mapping[name] || name] += num.to_i
|
123
|
+
overall += num.to_i
|
124
|
+
end
|
125
|
+
puts "#{overall} commits by #{authors.count} authors:"
|
126
|
+
puts authors.sort_by { |n,c| -c }.map { |e| a.format % e }.join(a.sep)
|
127
|
+
end
|
56
128
|
|
57
129
|
# PACKAGING ============================================================
|
58
130
|
|
@@ -92,25 +164,19 @@ if defined?(Gem)
|
|
92
164
|
SH
|
93
165
|
end
|
94
166
|
|
95
|
-
task '
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
join("\n")
|
110
|
-
# piece file back together and write...
|
111
|
-
manifest = " s.files = %w[\n#{files}\n ]\n"
|
112
|
-
spec = [head,manifest,tail].join(" # = MANIFEST =\n")
|
113
|
-
File.open(f.name, 'w') { |io| io.write(spec) }
|
114
|
-
puts "updated #{f.name}"
|
167
|
+
task 'release' => ['test', package('.gem')] do
|
168
|
+
if File.read("CHANGES") =~ /= \d\.\d\.\d . not yet released$/i
|
169
|
+
fail 'please update changes first'
|
170
|
+
end
|
171
|
+
|
172
|
+
sh <<-SH
|
173
|
+
gem install #{package('.gem')} --local &&
|
174
|
+
gem push #{package('.gem')} &&
|
175
|
+
git commit --allow-empty -a -m '#{source_version} release' &&
|
176
|
+
git tag -s v#{source_version} -m '#{source_version} release' &&
|
177
|
+
git tag -s #{source_version} -m '#{source_version} release' &&
|
178
|
+
git push && (git push sinatra || true) &&
|
179
|
+
git push --tags && (git push sinatra --tags || true)
|
180
|
+
SH
|
115
181
|
end
|
116
182
|
end
|
data/examples/chat.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
#!/usr/bin/env ruby -I ../lib -I lib
|
2
|
+
# coding: utf-8
|
3
|
+
require 'sinatra'
|
4
|
+
set :server, 'thin'
|
5
|
+
connections = []
|
6
|
+
|
7
|
+
get '/' do
|
8
|
+
halt erb(:login) unless params[:user]
|
9
|
+
erb :chat, :locals => { :user => params[:user].gsub(/\W/, '') }
|
10
|
+
end
|
11
|
+
|
12
|
+
get '/stream', :provides => 'text/event-stream' do
|
13
|
+
stream :keep_open do |out|
|
14
|
+
connections << out
|
15
|
+
out.callback { connections.delete(out) }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
post '/' do
|
20
|
+
connections.each { |out| out << "data: #{params[:msg]}\n\n" }
|
21
|
+
204 # response without entity body
|
22
|
+
end
|
23
|
+
|
24
|
+
__END__
|
25
|
+
|
26
|
+
@@ layout
|
27
|
+
<html>
|
28
|
+
<head>
|
29
|
+
<title>Super Simple Chat with Sinatra</title>
|
30
|
+
<meta charset="utf-8" />
|
31
|
+
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
|
32
|
+
</head>
|
33
|
+
<body><%= yield %></body>
|
34
|
+
</html>
|
35
|
+
|
36
|
+
@@ login
|
37
|
+
<form action='/'>
|
38
|
+
<label for='user'>User Name:</label>
|
39
|
+
<input name='user' value='' />
|
40
|
+
<input type='submit' value="GO!" />
|
41
|
+
</form>
|
42
|
+
|
43
|
+
@@ chat
|
44
|
+
<pre id='chat'></pre>
|
45
|
+
|
46
|
+
<script>
|
47
|
+
// reading
|
48
|
+
var es = new EventSource('/stream');
|
49
|
+
es.onmessage = function(e) { $('#chat').append(e.data + "\n") };
|
50
|
+
|
51
|
+
// writing
|
52
|
+
$("form").live("submit", function(e) {
|
53
|
+
$.post('/', {msg: "<%= user %>: " + $('#msg').val()});
|
54
|
+
$('#msg').val(''); $('#msg').focus();
|
55
|
+
e.preventDefault();
|
56
|
+
});
|
57
|
+
</script>
|
58
|
+
|
59
|
+
<form>
|
60
|
+
<input id='msg' placeholder='type message here...' />
|
61
|
+
</form>
|
data/examples/simple.rb
ADDED
data/examples/stream.ru
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# this example does *not* work properly with WEBrick
|
2
|
+
#
|
3
|
+
# run *one* of these:
|
4
|
+
#
|
5
|
+
# rackup -s mongrel stream.ru # gem install mongrel
|
6
|
+
# thin -R stream.ru start # gem install thin
|
7
|
+
# unicorn stream.ru # gem install unicorn
|
8
|
+
# puma stream.ru # gem install puma
|
9
|
+
|
10
|
+
require 'sinatra/base'
|
11
|
+
|
12
|
+
class Stream < Sinatra::Base
|
13
|
+
get '/' do
|
14
|
+
content_type :txt
|
15
|
+
|
16
|
+
stream do |out|
|
17
|
+
out << "It's gonna be legen -\n"
|
18
|
+
sleep 0.5
|
19
|
+
out << " (wait for it) \n"
|
20
|
+
sleep 1
|
21
|
+
out << "- dary!\n"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
run Stream
|
data/lib/sinatra.rb
CHANGED
data/lib/sinatra/base.rb
CHANGED
@@ -1,51 +1,60 @@
|
|
1
|
+
# external dependencies
|
2
|
+
require 'rack'
|
3
|
+
require 'tilt'
|
4
|
+
require "rack/protection"
|
5
|
+
|
6
|
+
# stdlib dependencies
|
1
7
|
require 'thread'
|
2
8
|
require 'time'
|
3
9
|
require 'uri'
|
4
|
-
require 'rack'
|
5
|
-
require 'rack/builder'
|
6
|
-
require 'sinatra/showexceptions'
|
7
10
|
|
8
|
-
#
|
9
|
-
|
10
|
-
|
11
|
-
if Tilt::VERSION < '0.8'
|
12
|
-
warn "WARN: sinatra requires tilt >= 0.8; you have #{Tilt::VERSION}. " +
|
13
|
-
"loading bundled version..."
|
14
|
-
Object.send :remove_const, :Tilt
|
15
|
-
raise LoadError
|
16
|
-
end
|
17
|
-
rescue LoadError
|
18
|
-
require 'sinatra/tilt'
|
19
|
-
end
|
11
|
+
# other files we need
|
12
|
+
require 'sinatra/showexceptions'
|
13
|
+
require 'sinatra/version'
|
20
14
|
|
21
15
|
module Sinatra
|
22
|
-
VERSION = '1.0'
|
23
|
-
|
24
16
|
# The request object. See Rack::Request for more info:
|
25
17
|
# http://rack.rubyforge.org/doc/classes/Rack/Request.html
|
26
18
|
class Request < Rack::Request
|
27
19
|
# Returns an array of acceptable media types for the response
|
28
20
|
def accept
|
29
|
-
@env['
|
21
|
+
@env['sinatra.accept'] ||= begin
|
22
|
+
entries = @env['HTTP_ACCEPT'].to_s.split(',')
|
23
|
+
entries.map { |e| accept_entry(e) }.sort_by(&:last).map(&:first)
|
24
|
+
end
|
30
25
|
end
|
31
26
|
|
32
|
-
def
|
33
|
-
|
27
|
+
def preferred_type(*types)
|
28
|
+
return accept.first if types.empty?
|
29
|
+
types.flatten!
|
30
|
+
accept.detect do |pattern|
|
31
|
+
type = types.detect { |t| File.fnmatch(pattern, t) }
|
32
|
+
return type if type
|
33
|
+
end
|
34
34
|
end
|
35
35
|
|
36
|
-
|
37
|
-
|
38
|
-
# XXX remove when we require rack > 1.1
|
39
|
-
if Rack.release < '1.1'
|
40
|
-
def params
|
41
|
-
self.GET.update(self.POST)
|
42
|
-
rescue EOFError, Errno::ESPIPE
|
43
|
-
self.GET
|
44
|
-
end
|
36
|
+
alias accept? preferred_type
|
37
|
+
alias secure? ssl?
|
45
38
|
|
46
|
-
|
47
|
-
|
48
|
-
|
39
|
+
def forwarded?
|
40
|
+
@env.include? "HTTP_X_FORWARDED_HOST"
|
41
|
+
end
|
42
|
+
|
43
|
+
def safe?
|
44
|
+
get? or head? or options? or trace?
|
45
|
+
end
|
46
|
+
|
47
|
+
def idempotent?
|
48
|
+
safe? or put? or delete?
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def accept_entry(entry)
|
54
|
+
type, *options = entry.delete(' ').split(';')
|
55
|
+
quality = 0 # we sort smallest first
|
56
|
+
options.delete_if { |e| quality = 1 - e[2..-1].to_f if e.start_with? 'q=' }
|
57
|
+
[type, [quality, type.count('*'), 1 - options.size]]
|
49
58
|
end
|
50
59
|
end
|
51
60
|
|
@@ -54,25 +63,48 @@ module Sinatra
|
|
54
63
|
# http://rack.rubyforge.org/doc/classes/Rack/Response.html
|
55
64
|
# http://rack.rubyforge.org/doc/classes/Rack/Response/Helpers.html
|
56
65
|
class Response < Rack::Response
|
66
|
+
def body=(value)
|
67
|
+
value = value.body while Rack::Response === value
|
68
|
+
@body = String === value ? [value.to_str] : value
|
69
|
+
end
|
70
|
+
|
71
|
+
def each
|
72
|
+
block_given? ? super : enum_for(:each)
|
73
|
+
end
|
74
|
+
|
57
75
|
def finish
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
76
|
+
if status.to_i / 100 == 1
|
77
|
+
headers.delete "Content-Length"
|
78
|
+
headers.delete "Content-Type"
|
79
|
+
elsif Array === body and not [204, 304].include?(status.to_i)
|
80
|
+
headers["Content-Length"] = body.inject(0) { |l, p| l + Rack::Utils.bytesize(p) }.to_s
|
81
|
+
end
|
82
|
+
|
83
|
+
# Rack::Response#finish sometimes returns self as response body. We don't want that.
|
84
|
+
status, headers, result = super
|
85
|
+
result = body if result == self
|
86
|
+
[status, headers, result]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Behaves exactly like Rack::CommonLogger with the notable exception that it does nothing,
|
91
|
+
# if another CommonLogger is already in the middleware chane.
|
92
|
+
class CommonLogger < Rack::CommonLogger
|
93
|
+
def call(env)
|
94
|
+
env['sinatra.commonlogger'] ? @app.call(env) : super
|
95
|
+
end
|
96
|
+
|
97
|
+
superclass.class_eval do
|
98
|
+
alias call_without_check call unless method_defined? :call_without_check
|
99
|
+
def call(env)
|
100
|
+
env['sinatra.commonlogger'] = true
|
101
|
+
call_without_check(env)
|
70
102
|
end
|
71
103
|
end
|
72
104
|
end
|
73
105
|
|
74
106
|
class NotFound < NameError #:nodoc:
|
75
|
-
def
|
107
|
+
def http_status; 404 end
|
76
108
|
end
|
77
109
|
|
78
110
|
# Methods available to routes, before/after filters, and views.
|
@@ -87,20 +119,50 @@ module Sinatra
|
|
87
119
|
# evaluation is deferred until the body is read with #each.
|
88
120
|
def body(value=nil, &block)
|
89
121
|
if block_given?
|
90
|
-
def block.each
|
122
|
+
def block.each; yield(call) end
|
91
123
|
response.body = block
|
92
|
-
|
124
|
+
elsif value
|
93
125
|
response.body = value
|
126
|
+
else
|
127
|
+
response.body
|
94
128
|
end
|
95
129
|
end
|
96
130
|
|
97
131
|
# Halt processing and redirect to the URI provided.
|
98
132
|
def redirect(uri, *args)
|
99
|
-
|
100
|
-
|
133
|
+
if env['HTTP_VERSION'] == 'HTTP/1.1' and env["REQUEST_METHOD"] != 'GET'
|
134
|
+
status 303
|
135
|
+
else
|
136
|
+
status 302
|
137
|
+
end
|
138
|
+
|
139
|
+
# According to RFC 2616 section 14.30, "the field value consists of a
|
140
|
+
# single absolute URI"
|
141
|
+
response['Location'] = uri(uri, settings.absolute_redirects?, settings.prefixed_redirects?)
|
101
142
|
halt(*args)
|
102
143
|
end
|
103
144
|
|
145
|
+
# Generates the absolute URI for a given path in the app.
|
146
|
+
# Takes Rack routers and reverse proxies into account.
|
147
|
+
def uri(addr = nil, absolute = true, add_script_name = true)
|
148
|
+
return addr if addr =~ /\A[A-z][A-z0-9\+\.\-]*:/
|
149
|
+
uri = [host = ""]
|
150
|
+
if absolute
|
151
|
+
host << "http#{'s' if request.secure?}://"
|
152
|
+
if request.forwarded? or request.port != (request.secure? ? 443 : 80)
|
153
|
+
host << request.host_with_port
|
154
|
+
else
|
155
|
+
host << request.host
|
156
|
+
end
|
157
|
+
end
|
158
|
+
uri << request.script_name.to_s if add_script_name
|
159
|
+
uri << (addr ? addr : request.path_info).to_s
|
160
|
+
File.join uri
|
161
|
+
end
|
162
|
+
|
163
|
+
alias url uri
|
164
|
+
alias to uri
|
165
|
+
|
104
166
|
# Halt processing and return the error status provided.
|
105
167
|
def error(code, body=nil)
|
106
168
|
code, body = 500, code.to_str if code.respond_to? :to_str
|
@@ -121,7 +183,12 @@ module Sinatra
|
|
121
183
|
|
122
184
|
# Access the underlying Rack session.
|
123
185
|
def session
|
124
|
-
|
186
|
+
request.session
|
187
|
+
end
|
188
|
+
|
189
|
+
# Access shared logger object.
|
190
|
+
def logger
|
191
|
+
request.logger
|
125
192
|
end
|
126
193
|
|
127
194
|
# Look up a media type by file extension in Rack's mime registry.
|
@@ -131,15 +198,21 @@ module Sinatra
|
|
131
198
|
|
132
199
|
# Set the Content-Type of the response body given a media type or file
|
133
200
|
# extension.
|
134
|
-
def content_type(type, params={})
|
135
|
-
|
201
|
+
def content_type(type = nil, params={})
|
202
|
+
return response['Content-Type'] unless type
|
203
|
+
default = params.delete :default
|
204
|
+
mime_type = mime_type(type) || default
|
136
205
|
fail "Unknown media type: %p" % type if mime_type.nil?
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
206
|
+
mime_type = mime_type.dup
|
207
|
+
unless params.include? :charset or settings.add_charset.all? { |p| not p === mime_type }
|
208
|
+
params[:charset] = params.delete('charset') || settings.default_encoding
|
209
|
+
end
|
210
|
+
params.delete :charset if mime_type.include? 'charset'
|
211
|
+
unless params.empty?
|
212
|
+
mime_type << (mime_type.include?(';') ? ', ' : ';')
|
213
|
+
mime_type << params.map { |kv| kv.join('=') }.join(', ')
|
142
214
|
end
|
215
|
+
response['Content-Type'] = mime_type
|
143
216
|
end
|
144
217
|
|
145
218
|
# Set the Content-Disposition to "attachment" with the specified filename,
|
@@ -149,20 +222,16 @@ module Sinatra
|
|
149
222
|
if filename
|
150
223
|
params = '; filename="%s"' % File.basename(filename)
|
151
224
|
response['Content-Disposition'] << params
|
225
|
+
ext = File.extname(filename)
|
226
|
+
content_type(ext) unless response['Content-Type'] or ext.empty?
|
152
227
|
end
|
153
228
|
end
|
154
229
|
|
155
230
|
# Use the contents of the file at +path+ as the response body.
|
156
231
|
def send_file(path, opts={})
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
content_type mime_type(opts[:type]) ||
|
161
|
-
mime_type(File.extname(path)) ||
|
162
|
-
response['Content-Type'] ||
|
163
|
-
'application/octet-stream'
|
164
|
-
|
165
|
-
response['Content-Length'] ||= (opts[:length] || stat.size).to_s
|
232
|
+
if opts[:type] or not response['Content-Type']
|
233
|
+
content_type opts[:type] || File.extname(path), :default => 'application/octet-stream'
|
234
|
+
end
|
166
235
|
|
167
236
|
if opts[:disposition] == 'attachment' || opts[:filename]
|
168
237
|
attachment opts[:filename] || path
|
@@ -170,21 +239,93 @@ module Sinatra
|
|
170
239
|
response['Content-Disposition'] = 'inline'
|
171
240
|
end
|
172
241
|
|
173
|
-
|
242
|
+
last_modified opts[:last_modified] if opts[:last_modified]
|
243
|
+
|
244
|
+
file = Rack::File.new nil
|
245
|
+
file.path = path
|
246
|
+
result = file.serving env
|
247
|
+
result[1].each { |k,v| headers[k] ||= v }
|
248
|
+
headers['Content-Length'] = result[1]['Content-Length']
|
249
|
+
halt opts[:status] || result[0], result[2]
|
174
250
|
rescue Errno::ENOENT
|
175
251
|
not_found
|
176
252
|
end
|
177
253
|
|
178
|
-
#
|
179
|
-
#
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
254
|
+
# Class of the response body in case you use #stream.
|
255
|
+
#
|
256
|
+
# Three things really matter: The front and back block (back being the
|
257
|
+
# blog generating content, front the one sending it to the client) and
|
258
|
+
# the scheduler, integrating with whatever concurrency feature the Rack
|
259
|
+
# handler is using.
|
260
|
+
#
|
261
|
+
# Scheduler has to respond to defer and schedule.
|
262
|
+
class Stream
|
263
|
+
def self.schedule(*) yield end
|
264
|
+
def self.defer(*) yield end
|
265
|
+
|
266
|
+
def initialize(scheduler = self.class, keep_open = false, &back)
|
267
|
+
@back, @scheduler, @keep_open = back.to_proc, scheduler, keep_open
|
268
|
+
@callbacks, @closed = [], false
|
269
|
+
end
|
270
|
+
|
271
|
+
def close
|
272
|
+
return if @closed
|
273
|
+
@closed = true
|
274
|
+
@scheduler.schedule { @callbacks.each { |c| c.call }}
|
275
|
+
end
|
276
|
+
|
277
|
+
def each(&front)
|
278
|
+
@front = front
|
279
|
+
@scheduler.defer do
|
280
|
+
begin
|
281
|
+
@back.call(self)
|
282
|
+
rescue Exception => e
|
283
|
+
@scheduler.schedule { raise e }
|
284
|
+
end
|
285
|
+
close unless @keep_open
|
186
286
|
end
|
187
287
|
end
|
288
|
+
|
289
|
+
def <<(data)
|
290
|
+
@scheduler.schedule { @front.call(data.to_s) }
|
291
|
+
self
|
292
|
+
end
|
293
|
+
|
294
|
+
def callback(&block)
|
295
|
+
return yield if @closed
|
296
|
+
@callbacks << block
|
297
|
+
end
|
298
|
+
|
299
|
+
alias errback callback
|
300
|
+
end
|
301
|
+
|
302
|
+
# Allows to start sending data to the client even though later parts of
|
303
|
+
# the response body have not yet been generated.
|
304
|
+
#
|
305
|
+
# The close parameter specifies whether Stream#close should be called
|
306
|
+
# after the block has been executed. This is only relevant for evented
|
307
|
+
# servers like Thin or Rainbows.
|
308
|
+
def stream(keep_open = false)
|
309
|
+
scheduler = env['async.callback'] ? EventMachine : Stream
|
310
|
+
current = @params.dup
|
311
|
+
|
312
|
+
block = proc do |out|
|
313
|
+
begin
|
314
|
+
original, @params = @params, current
|
315
|
+
yield(out)
|
316
|
+
ensure
|
317
|
+
@params = original if original
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
out = Stream.new(scheduler, keep_open, &block)
|
322
|
+
|
323
|
+
if env['async.close']
|
324
|
+
env['async.close'].callback { out.close }
|
325
|
+
env['async.close'].errback { out.close }
|
326
|
+
end
|
327
|
+
|
328
|
+
body out
|
188
329
|
end
|
189
330
|
|
190
331
|
# Specify response freshness policy for HTTP caches (Cache-Control header).
|
@@ -206,8 +347,12 @@ module Sinatra
|
|
206
347
|
hash = {}
|
207
348
|
end
|
208
349
|
|
209
|
-
values
|
210
|
-
hash.each
|
350
|
+
values.map! { |value| value.to_s.tr('_','-') }
|
351
|
+
hash.each do |key, value|
|
352
|
+
key = key.to_s.tr('_', '-')
|
353
|
+
value = value.to_i if key == "max-age"
|
354
|
+
values << [key, value].join('=')
|
355
|
+
end
|
211
356
|
|
212
357
|
response['Cache-Control'] = values.join(', ') if values.any?
|
213
358
|
end
|
@@ -224,12 +369,12 @@ module Sinatra
|
|
224
369
|
def expires(amount, *values)
|
225
370
|
values << {} unless values.last.kind_of?(Hash)
|
226
371
|
|
227
|
-
if amount.
|
228
|
-
|
229
|
-
time = amount.to_time
|
230
|
-
else
|
372
|
+
if amount.is_a? Integer
|
373
|
+
time = Time.now + amount.to_i
|
231
374
|
max_age = amount
|
232
|
-
|
375
|
+
else
|
376
|
+
time = time_for amount
|
377
|
+
max_age = time - Time.now
|
233
378
|
end
|
234
379
|
|
235
380
|
values.last.merge!(:max_age => max_age)
|
@@ -242,16 +387,27 @@ module Sinatra
|
|
242
387
|
# and halt if conditional GET matches. The +time+ argument is a Time,
|
243
388
|
# DateTime, or other object that responds to +to_time+.
|
244
389
|
#
|
245
|
-
# When the current request includes an 'If-Modified-Since' header that
|
246
|
-
#
|
247
|
-
# '304 Not Modified' response.
|
390
|
+
# When the current request includes an 'If-Modified-Since' header that is
|
391
|
+
# equal or later than the time specified, execution is immediately halted
|
392
|
+
# with a '304 Not Modified' response.
|
248
393
|
def last_modified(time)
|
249
394
|
return unless time
|
250
|
-
time =
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
395
|
+
time = time_for time
|
396
|
+
response['Last-Modified'] = time.httpdate
|
397
|
+
return if env['HTTP_IF_NONE_MATCH']
|
398
|
+
|
399
|
+
if status == 200 and env['HTTP_IF_MODIFIED_SINCE']
|
400
|
+
# compare based on seconds since epoch
|
401
|
+
since = Time.httpdate(env['HTTP_IF_MODIFIED_SINCE']).to_i
|
402
|
+
halt 304 if since >= time.to_i
|
403
|
+
end
|
404
|
+
|
405
|
+
if (success? or status == 412) and env['HTTP_IF_UNMODIFIED_SINCE']
|
406
|
+
# compare based on seconds since epoch
|
407
|
+
since = Time.httpdate(env['HTTP_IF_UNMODIFIED_SINCE']).to_i
|
408
|
+
halt 412 if since < time.to_i
|
409
|
+
end
|
410
|
+
rescue ArgumentError
|
255
411
|
end
|
256
412
|
|
257
413
|
# Set the response entity tag (HTTP 'ETag' header) and halt if conditional
|
@@ -263,24 +419,103 @@ module Sinatra
|
|
263
419
|
# When the current request includes an 'If-None-Match' header with a
|
264
420
|
# matching etag, execution is immediately halted. If the request method is
|
265
421
|
# GET or HEAD, a '304 Not Modified' response is sent.
|
266
|
-
def etag(value,
|
267
|
-
|
422
|
+
def etag(value, options = {})
|
423
|
+
# Before touching this code, please double check RFC 2616 14.24 and 14.26.
|
424
|
+
options = {:kind => options} unless Hash === options
|
425
|
+
kind = options[:kind] || :strong
|
426
|
+
new_resource = options.fetch(:new_resource) { request.post? }
|
427
|
+
|
428
|
+
unless [:strong, :weak].include?(kind)
|
429
|
+
raise ArgumentError, ":strong or :weak expected"
|
430
|
+
end
|
431
|
+
|
268
432
|
value = '"%s"' % value
|
269
433
|
value = 'W/' + value if kind == :weak
|
270
434
|
response['ETag'] = value
|
271
435
|
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
436
|
+
if success? or status == 304
|
437
|
+
if etag_matches? env['HTTP_IF_NONE_MATCH'], new_resource
|
438
|
+
halt(request.safe? ? 304 : 412)
|
439
|
+
end
|
440
|
+
|
441
|
+
if env['HTTP_IF_MATCH']
|
442
|
+
halt 412 unless etag_matches? env['HTTP_IF_MATCH'], new_resource
|
443
|
+
end
|
444
|
+
end
|
445
|
+
end
|
446
|
+
|
447
|
+
# Sugar for redirect (example: redirect back)
|
448
|
+
def back
|
449
|
+
request.referer
|
450
|
+
end
|
451
|
+
|
452
|
+
# whether or not the status is set to 1xx
|
453
|
+
def informational?
|
454
|
+
status.between? 100, 199
|
455
|
+
end
|
456
|
+
|
457
|
+
# whether or not the status is set to 2xx
|
458
|
+
def success?
|
459
|
+
status.between? 200, 299
|
460
|
+
end
|
461
|
+
|
462
|
+
# whether or not the status is set to 3xx
|
463
|
+
def redirect?
|
464
|
+
status.between? 300, 399
|
465
|
+
end
|
466
|
+
|
467
|
+
# whether or not the status is set to 4xx
|
468
|
+
def client_error?
|
469
|
+
status.between? 400, 499
|
470
|
+
end
|
471
|
+
|
472
|
+
# whether or not the status is set to 5xx
|
473
|
+
def server_error?
|
474
|
+
status.between? 500, 599
|
475
|
+
end
|
476
|
+
|
477
|
+
# whether or not the status is set to 404
|
478
|
+
def not_found?
|
479
|
+
status == 404
|
480
|
+
end
|
481
|
+
|
482
|
+
# Generates a Time object from the given value.
|
483
|
+
# Used by #expires and #last_modified.
|
484
|
+
def time_for(value)
|
485
|
+
if value.respond_to? :to_time
|
486
|
+
value.to_time
|
487
|
+
elsif value.is_a? Time
|
488
|
+
value
|
489
|
+
elsif value.respond_to? :new_offset
|
490
|
+
# DateTime#to_time does the same on 1.9
|
491
|
+
d = value.new_offset 0
|
492
|
+
t = Time.utc d.year, d.mon, d.mday, d.hour, d.min, d.sec + d.sec_fraction
|
493
|
+
t.getlocal
|
494
|
+
elsif value.respond_to? :mday
|
495
|
+
# Date#to_time does the same on 1.9
|
496
|
+
Time.local(value.year, value.mon, value.mday)
|
497
|
+
elsif value.is_a? Numeric
|
498
|
+
Time.at value
|
499
|
+
else
|
500
|
+
Time.parse value.to_s
|
276
501
|
end
|
502
|
+
rescue ArgumentError => boom
|
503
|
+
raise boom
|
504
|
+
rescue Exception
|
505
|
+
raise ArgumentError, "unable to convert #{value.inspect} to a Time object"
|
277
506
|
end
|
278
507
|
|
279
|
-
|
280
|
-
def back ; request.referer ; end
|
508
|
+
private
|
281
509
|
|
510
|
+
# Helper method checking if a ETag value list includes the current ETag.
|
511
|
+
def etag_matches?(list, new_resource = request.post?)
|
512
|
+
return !new_resource if list == '*'
|
513
|
+
list.to_s.split(/\s*,\s*/).include? response['ETag']
|
514
|
+
end
|
282
515
|
end
|
283
516
|
|
517
|
+
private
|
518
|
+
|
284
519
|
# Template rendering methods. Each method takes the name of a template
|
285
520
|
# to render as a Symbol and returns a String with the rendered output,
|
286
521
|
# as well as an optional hash with additional options.
|
@@ -290,20 +525,32 @@ module Sinatra
|
|
290
525
|
# that will be rendered.
|
291
526
|
#
|
292
527
|
# Possible options are:
|
293
|
-
# :
|
294
|
-
#
|
295
|
-
#
|
296
|
-
#
|
528
|
+
# :content_type The content type to use, same arguments as content_type.
|
529
|
+
# :layout If set to false, no layout is rendered, otherwise
|
530
|
+
# the specified layout is used (Ignored for `sass` and `less`)
|
531
|
+
# :layout_engine Engine to use for rendering the layout.
|
532
|
+
# :locals A hash with local variables that should be available
|
533
|
+
# in the template
|
534
|
+
# :scope If set, template is evaluate with the binding of the given
|
535
|
+
# object rather than the application instance.
|
536
|
+
# :views Views directory to use.
|
297
537
|
module Templates
|
298
|
-
|
538
|
+
module ContentTyped
|
539
|
+
attr_accessor :content_type
|
540
|
+
end
|
541
|
+
|
542
|
+
def initialize
|
543
|
+
super
|
544
|
+
@default_layout = :layout
|
545
|
+
end
|
299
546
|
|
300
547
|
def erb(template, options={}, locals={})
|
301
|
-
options[:outvar] = '@_out_buf'
|
302
548
|
render :erb, template, options, locals
|
303
549
|
end
|
304
550
|
|
305
551
|
def erubis(template, options={}, locals={})
|
306
|
-
|
552
|
+
warn "Sinatra::Templates#erubis is deprecated and will be removed, use #erb instead.\n" \
|
553
|
+
"If you have Erubis installed, it will be used automatically."
|
307
554
|
render :erubis, template, options, locals
|
308
555
|
end
|
309
556
|
|
@@ -312,69 +559,157 @@ module Sinatra
|
|
312
559
|
end
|
313
560
|
|
314
561
|
def sass(template, options={}, locals={})
|
315
|
-
options
|
562
|
+
options.merge! :layout => false, :default_content_type => :css
|
316
563
|
render :sass, template, options, locals
|
317
564
|
end
|
318
565
|
|
566
|
+
def scss(template, options={}, locals={})
|
567
|
+
options.merge! :layout => false, :default_content_type => :css
|
568
|
+
render :scss, template, options, locals
|
569
|
+
end
|
570
|
+
|
319
571
|
def less(template, options={}, locals={})
|
320
|
-
options
|
572
|
+
options.merge! :layout => false, :default_content_type => :css
|
321
573
|
render :less, template, options, locals
|
322
574
|
end
|
323
575
|
|
324
576
|
def builder(template=nil, options={}, locals={}, &block)
|
577
|
+
options[:default_content_type] = :xml
|
578
|
+
render_ruby(:builder, template, options, locals, &block)
|
579
|
+
end
|
580
|
+
|
581
|
+
def liquid(template, options={}, locals={})
|
582
|
+
render :liquid, template, options, locals
|
583
|
+
end
|
584
|
+
|
585
|
+
def markdown(template, options={}, locals={})
|
586
|
+
render :markdown, template, options, locals
|
587
|
+
end
|
588
|
+
|
589
|
+
def textile(template, options={}, locals={})
|
590
|
+
render :textile, template, options, locals
|
591
|
+
end
|
592
|
+
|
593
|
+
def rdoc(template, options={}, locals={})
|
594
|
+
render :rdoc, template, options, locals
|
595
|
+
end
|
596
|
+
|
597
|
+
def radius(template, options={}, locals={})
|
598
|
+
render :radius, template, options, locals
|
599
|
+
end
|
600
|
+
|
601
|
+
def markaby(template=nil, options={}, locals={}, &block)
|
602
|
+
render_ruby(:mab, template, options, locals, &block)
|
603
|
+
end
|
604
|
+
|
605
|
+
def coffee(template, options={}, locals={})
|
606
|
+
options.merge! :layout => false, :default_content_type => :js
|
607
|
+
render :coffee, template, options, locals
|
608
|
+
end
|
609
|
+
|
610
|
+
def nokogiri(template=nil, options={}, locals={}, &block)
|
611
|
+
options[:default_content_type] = :xml
|
612
|
+
render_ruby(:nokogiri, template, options, locals, &block)
|
613
|
+
end
|
614
|
+
|
615
|
+
def slim(template, options={}, locals={})
|
616
|
+
render :slim, template, options, locals
|
617
|
+
end
|
618
|
+
|
619
|
+
def creole(template, options={}, locals={})
|
620
|
+
render :creole, template, options, locals
|
621
|
+
end
|
622
|
+
|
623
|
+
def yajl(template, options={}, locals={})
|
624
|
+
options[:default_content_type] = :json
|
625
|
+
render :yajl, template, options, locals
|
626
|
+
end
|
627
|
+
|
628
|
+
# Calls the given block for every possible template file in views,
|
629
|
+
# named name.ext, where ext is registered on engine.
|
630
|
+
def find_template(views, name, engine)
|
631
|
+
yield ::File.join(views, "#{name}.#{@preferred_extension}")
|
632
|
+
Tilt.mappings.each do |ext, engines|
|
633
|
+
next unless ext != @preferred_extension and engines.include? engine
|
634
|
+
yield ::File.join(views, "#{name}.#{ext}")
|
635
|
+
end
|
636
|
+
end
|
637
|
+
|
638
|
+
private
|
639
|
+
# logic shared between builder and nokogiri
|
640
|
+
def render_ruby(engine, template, options={}, locals={}, &block)
|
325
641
|
options, template = template, nil if template.is_a?(Hash)
|
326
642
|
template = Proc.new { block } if template.nil?
|
327
|
-
render
|
643
|
+
render engine, template, options, locals
|
328
644
|
end
|
329
645
|
|
330
|
-
private
|
331
646
|
def render(engine, data, options={}, locals={}, &block)
|
332
647
|
# merge app-level options
|
333
648
|
options = settings.send(engine).merge(options) if settings.respond_to?(engine)
|
649
|
+
options[:outvar] ||= '@_out_buf'
|
650
|
+
options[:default_encoding] ||= settings.default_encoding
|
334
651
|
|
335
652
|
# extract generic options
|
336
|
-
locals
|
337
|
-
views
|
338
|
-
layout
|
339
|
-
|
653
|
+
locals = options.delete(:locals) || locals || {}
|
654
|
+
views = options.delete(:views) || settings.views || "./views"
|
655
|
+
layout = options.delete(:layout)
|
656
|
+
eat_errors = layout.nil?
|
657
|
+
layout = @default_layout if layout.nil? or layout == true
|
658
|
+
content_type = options.delete(:content_type) || options.delete(:default_content_type)
|
659
|
+
layout_engine = options.delete(:layout_engine) || engine
|
660
|
+
scope = options.delete(:scope) || self
|
340
661
|
|
341
662
|
# compile and render template
|
342
|
-
|
343
|
-
|
663
|
+
begin
|
664
|
+
layout_was = @default_layout
|
665
|
+
@default_layout = false
|
666
|
+
template = compile_template(engine, data, options, views)
|
667
|
+
output = template.render(scope, locals, &block)
|
668
|
+
ensure
|
669
|
+
@default_layout = layout_was
|
670
|
+
end
|
344
671
|
|
345
672
|
# render layout
|
346
673
|
if layout
|
347
|
-
|
348
|
-
|
349
|
-
output = render(engine, layout, options, locals) { output }
|
350
|
-
rescue Errno::ENOENT
|
351
|
-
end
|
674
|
+
options = options.merge(:views => views, :layout => false, :eat_errors => eat_errors, :scope => scope)
|
675
|
+
catch(:layout_missing) { return render(layout_engine, layout, options, locals) { output } }
|
352
676
|
end
|
353
677
|
|
678
|
+
output.extend(ContentTyped).content_type = content_type if content_type
|
354
679
|
output
|
355
680
|
end
|
356
681
|
|
357
682
|
def compile_template(engine, data, options, views)
|
358
|
-
|
683
|
+
eat_errors = options.delete :eat_errors
|
684
|
+
template_cache.fetch engine, data, options do
|
359
685
|
template = Tilt[engine]
|
360
686
|
raise "Template engine not found: #{engine}" if template.nil?
|
361
687
|
|
362
|
-
case
|
363
|
-
when
|
364
|
-
body, path, line =
|
688
|
+
case data
|
689
|
+
when Symbol
|
690
|
+
body, path, line = settings.templates[data]
|
365
691
|
if body
|
366
692
|
body = body.call if body.respond_to?(:call)
|
367
693
|
template.new(path, line.to_i, options) { body }
|
368
694
|
else
|
369
|
-
|
695
|
+
found = false
|
696
|
+
@preferred_extension = engine.to_s
|
697
|
+
find_template(views, data, template) do |file|
|
698
|
+
path ||= file # keep the initial path rather than the last one
|
699
|
+
if found = File.exists?(file)
|
700
|
+
path = file
|
701
|
+
break
|
702
|
+
end
|
703
|
+
end
|
704
|
+
throw :layout_missing if eat_errors and not found
|
370
705
|
template.new(path, 1, options)
|
371
706
|
end
|
372
|
-
when
|
707
|
+
when Proc, String
|
373
708
|
body = data.is_a?(String) ? Proc.new { data } : data
|
374
|
-
path, line =
|
709
|
+
path, line = settings.caller_locations.first
|
375
710
|
template.new(path, line.to_i, options, &body)
|
376
711
|
else
|
377
|
-
raise ArgumentError
|
712
|
+
raise ArgumentError, "Sorry, don't know how to render #{data.inspect}."
|
378
713
|
end
|
379
714
|
end
|
380
715
|
end
|
@@ -387,8 +722,10 @@ module Sinatra
|
|
387
722
|
include Templates
|
388
723
|
|
389
724
|
attr_accessor :app
|
725
|
+
attr_reader :template_cache
|
390
726
|
|
391
727
|
def initialize(app=nil)
|
728
|
+
super()
|
392
729
|
@app = app
|
393
730
|
@template_cache = Tilt::Cache.new
|
394
731
|
yield self if block_given?
|
@@ -401,34 +738,44 @@ module Sinatra
|
|
401
738
|
|
402
739
|
attr_accessor :env, :request, :response, :params
|
403
740
|
|
404
|
-
def call!(env)
|
741
|
+
def call!(env) # :nodoc:
|
405
742
|
@env = env
|
406
743
|
@request = Request.new(env)
|
407
744
|
@response = Response.new
|
408
745
|
@params = indifferent_params(@request.params)
|
409
|
-
|
746
|
+
template_cache.clear if settings.reload_templates
|
747
|
+
force_encoding(@params)
|
410
748
|
|
749
|
+
@response['Content-Type'] = nil
|
411
750
|
invoke { dispatch! }
|
412
751
|
invoke { error_block!(response.status) }
|
413
752
|
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
body = []
|
421
|
-
header.delete('Content-Length') if header['Content-Length'] == '0'
|
753
|
+
unless @response['Content-Type']
|
754
|
+
if Array === body and body[0].respond_to? :content_type
|
755
|
+
content_type body[0].content_type
|
756
|
+
else
|
757
|
+
content_type :html
|
758
|
+
end
|
422
759
|
end
|
423
760
|
|
424
|
-
|
761
|
+
@response.finish
|
762
|
+
end
|
763
|
+
|
764
|
+
# Access settings defined with Base.set.
|
765
|
+
def self.settings
|
766
|
+
self
|
425
767
|
end
|
426
768
|
|
427
769
|
# Access settings defined with Base.set.
|
428
770
|
def settings
|
429
|
-
self.class
|
771
|
+
self.class.settings
|
772
|
+
end
|
773
|
+
|
774
|
+
def options
|
775
|
+
warn "Sinatra::Base#options is deprecated and will be removed, " \
|
776
|
+
"use #settings instead."
|
777
|
+
settings
|
430
778
|
end
|
431
|
-
alias_method :options, :settings
|
432
779
|
|
433
780
|
# Exit the current block, halts any further processing
|
434
781
|
# of the request, and returns the specified response.
|
@@ -447,7 +794,7 @@ module Sinatra
|
|
447
794
|
# Forward the request to the downstream app -- middleware only.
|
448
795
|
def forward
|
449
796
|
fail "downstream app not set" unless @app.respond_to? :call
|
450
|
-
status, headers, body = @app.call
|
797
|
+
status, headers, body = @app.call env
|
451
798
|
@response.status = status
|
452
799
|
@response.body = body
|
453
800
|
@response.headers.merge! headers
|
@@ -455,70 +802,58 @@ module Sinatra
|
|
455
802
|
end
|
456
803
|
|
457
804
|
private
|
458
|
-
# Run
|
459
|
-
def
|
460
|
-
|
461
|
-
base.
|
462
|
-
end
|
463
|
-
|
464
|
-
# Run after filters defined on the class and all superclasses.
|
465
|
-
def after_filter!(base=self.class)
|
466
|
-
after_filter!(base.superclass) if base.superclass.respond_to?(:after_filters)
|
467
|
-
base.after_filters.each { |block| instance_eval(&block) }
|
805
|
+
# Run filters defined on the class and all superclasses.
|
806
|
+
def filter!(type, base = settings)
|
807
|
+
filter! type, base.superclass if base.superclass.respond_to?(:filters)
|
808
|
+
base.filters[type].each { |args| process_route(*args) }
|
468
809
|
end
|
469
810
|
|
470
811
|
# Run routes defined on the class and all superclasses.
|
471
|
-
def route!(base=
|
812
|
+
def route!(base = settings, pass_block=nil)
|
472
813
|
if routes = base.routes[@request.request_method]
|
473
|
-
original_params = @params
|
474
|
-
path = unescape(@request.path_info)
|
475
|
-
|
476
814
|
routes.each do |pattern, keys, conditions, block|
|
477
|
-
|
478
|
-
|
479
|
-
params =
|
480
|
-
if keys.any?
|
481
|
-
keys.zip(values).inject({}) do |hash,(k,v)|
|
482
|
-
if k == 'splat'
|
483
|
-
(hash[k] ||= []) << v
|
484
|
-
else
|
485
|
-
hash[k] = v
|
486
|
-
end
|
487
|
-
hash
|
488
|
-
end
|
489
|
-
elsif values.any?
|
490
|
-
{'captures' => values}
|
491
|
-
else
|
492
|
-
{}
|
493
|
-
end
|
494
|
-
@params = original_params.merge(params)
|
495
|
-
@block_params = values
|
496
|
-
|
497
|
-
pass_block = catch(:pass) do
|
498
|
-
conditions.each { |cond|
|
499
|
-
throw :pass if instance_eval(&cond) == false }
|
500
|
-
route_eval(&block)
|
501
|
-
end
|
815
|
+
pass_block = process_route(pattern, keys, conditions) do |*args|
|
816
|
+
route_eval { block[*args] }
|
502
817
|
end
|
503
818
|
end
|
504
|
-
|
505
|
-
@params = original_params
|
506
819
|
end
|
507
820
|
|
508
821
|
# Run routes defined in superclass.
|
509
822
|
if base.superclass.respond_to?(:routes)
|
510
|
-
route!
|
511
|
-
return
|
823
|
+
return route!(base.superclass, pass_block)
|
512
824
|
end
|
513
825
|
|
514
826
|
route_eval(&pass_block) if pass_block
|
515
|
-
|
516
827
|
route_missing
|
517
828
|
end
|
518
829
|
|
519
830
|
# Run a route block and throw :halt with the result.
|
520
|
-
def route_eval
|
521
|
-
throw :halt,
|
831
|
+
def route_eval
|
832
|
+
throw :halt, yield
|
833
|
+
end
|
834
|
+
|
835
|
+
# If the current request matches pattern and conditions, fill params
|
836
|
+
# with keys and call the given block.
|
837
|
+
# Revert params afterwards.
|
838
|
+
#
|
839
|
+
# Returns pass block.
|
840
|
+
def process_route(pattern, keys, conditions, block = nil, values = [])
|
841
|
+
route = @request.path_info
|
842
|
+
route = '/' if route.empty? and not settings.empty_path_info?
|
843
|
+
return unless match = pattern.match(route)
|
844
|
+
values += match.captures.to_a.map { |v| force_encoding URI.decode_www_form_component(v) if v }
|
845
|
+
|
846
|
+
if values.any?
|
847
|
+
original, @params = params, params.merge('splat' => [], 'captures' => values)
|
848
|
+
keys.zip(values) { |k,v| Array === @params[k] ? @params[k] << v : @params[k] = v if v }
|
849
|
+
end
|
850
|
+
|
851
|
+
catch(:pass) do
|
852
|
+
conditions.each { |c| throw :pass if c.bind(self).call == false }
|
853
|
+
block ? block[self, values] : yield(self, values)
|
854
|
+
end
|
855
|
+
ensure
|
856
|
+
@params = original if original
|
522
857
|
end
|
523
858
|
|
524
859
|
# No matching route was found or all routes passed. The default
|
@@ -537,130 +872,122 @@ module Sinatra
|
|
537
872
|
# Attempt to serve static files from public directory. Throws :halt when
|
538
873
|
# a matching file is found, returns nil otherwise.
|
539
874
|
def static!
|
540
|
-
return if (public_dir = settings.
|
875
|
+
return if (public_dir = settings.public_folder).nil?
|
541
876
|
public_dir = File.expand_path(public_dir)
|
542
877
|
|
543
878
|
path = File.expand_path(public_dir + unescape(request.path_info))
|
544
|
-
return
|
545
|
-
return unless File.file?(path)
|
879
|
+
return unless path.start_with?(public_dir) and File.file?(path)
|
546
880
|
|
547
881
|
env['sinatra.static_file'] = path
|
882
|
+
cache_control(*settings.static_cache_control) if settings.static_cache_control?
|
548
883
|
send_file path, :disposition => nil
|
549
884
|
end
|
550
885
|
|
551
886
|
# Enable string or symbol key access to the nested params hash.
|
552
|
-
def indifferent_params(
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
887
|
+
def indifferent_params(object)
|
888
|
+
case object
|
889
|
+
when Hash
|
890
|
+
new_hash = indifferent_hash
|
891
|
+
object.each { |key, value| new_hash[key] = indifferent_params(value) }
|
892
|
+
new_hash
|
893
|
+
when Array
|
894
|
+
object.map { |item| indifferent_params(item) }
|
895
|
+
else
|
896
|
+
object
|
557
897
|
end
|
558
898
|
end
|
559
899
|
|
900
|
+
# Creates a Hash with indifferent access.
|
560
901
|
def indifferent_hash
|
561
902
|
Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
|
562
903
|
end
|
563
904
|
|
564
905
|
# Run the block with 'throw :halt' support and apply result to the response.
|
565
|
-
def invoke
|
566
|
-
res = catch(:halt) {
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
if Fixnum === res.first
|
575
|
-
if res.length == 3
|
576
|
-
@response.status, headers, body = res
|
577
|
-
@response.body = body if body
|
578
|
-
headers.each { |k, v| @response.headers[k] = v } if headers
|
579
|
-
elsif res.length == 2
|
580
|
-
@response.status = res.first
|
581
|
-
@response.body = res.last
|
582
|
-
else
|
583
|
-
raise TypeError, "#{res.inspect} not supported"
|
584
|
-
end
|
585
|
-
else
|
586
|
-
@response.body = res
|
587
|
-
end
|
588
|
-
when res.respond_to?(:each)
|
589
|
-
@response.body = res
|
590
|
-
when (100...599) === res
|
591
|
-
@response.status = res
|
906
|
+
def invoke
|
907
|
+
res = catch(:halt) { yield }
|
908
|
+
res = [res] if Fixnum === res or String === res
|
909
|
+
if Array === res and Fixnum === res.first
|
910
|
+
status(res.shift)
|
911
|
+
body(res.pop)
|
912
|
+
headers(*res)
|
913
|
+
elsif res.respond_to? :each
|
914
|
+
body res
|
592
915
|
end
|
593
|
-
|
594
|
-
res
|
916
|
+
nil # avoid double setting the same response tuple twice
|
595
917
|
end
|
596
918
|
|
597
919
|
# Dispatch a request with error handling.
|
598
920
|
def dispatch!
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
921
|
+
invoke do
|
922
|
+
static! if settings.static? && (request.get? || request.head?)
|
923
|
+
filter! :before
|
924
|
+
route!
|
925
|
+
end
|
604
926
|
rescue ::Exception => boom
|
605
|
-
handle_exception!(boom)
|
927
|
+
invoke { handle_exception!(boom) }
|
606
928
|
ensure
|
607
|
-
|
608
|
-
end
|
609
|
-
|
610
|
-
def handle_not_found!(boom)
|
611
|
-
@env['sinatra.error'] = boom
|
612
|
-
@response.status = 404
|
613
|
-
@response.headers['X-Cascade'] = 'pass'
|
614
|
-
@response.body = ['<h1>Not Found</h1>']
|
615
|
-
error_block! boom.class, NotFound
|
929
|
+
filter! :after unless env['sinatra.static_file']
|
616
930
|
end
|
617
931
|
|
932
|
+
# Error handling during requests.
|
618
933
|
def handle_exception!(boom)
|
619
934
|
@env['sinatra.error'] = boom
|
620
935
|
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
raise boom
|
936
|
+
if boom.respond_to? :http_status
|
937
|
+
status(boom.http_status)
|
938
|
+
elsif settings.use_code? and boom.respond_to? :code and boom.code.between? 400, 599
|
939
|
+
status(boom.code)
|
940
|
+
warn "Using #{boom.class}#code (#{status}) for setting status code. This is deprecated. " \
|
941
|
+
"Use #http_status instead. If this happens unintentional, please \`disable :use_code\`" \
|
942
|
+
" in your application."
|
629
943
|
else
|
630
|
-
|
944
|
+
status(500)
|
945
|
+
end
|
946
|
+
|
947
|
+
status(500) unless status.between? 400, 599
|
948
|
+
|
949
|
+
if server_error?
|
950
|
+
dump_errors! boom if settings.dump_errors?
|
951
|
+
raise boom if settings.show_exceptions? and settings.show_exceptions != :after_handler
|
631
952
|
end
|
953
|
+
|
954
|
+
if not_found?
|
955
|
+
headers['X-Cascade'] = 'pass'
|
956
|
+
body '<h1>Not Found</h1>'
|
957
|
+
end
|
958
|
+
|
959
|
+
res = error_block!(boom.class, boom) || error_block!(status, boom)
|
960
|
+
return res if res or not server_error?
|
961
|
+
raise boom if settings.raise_errors? or settings.show_exceptions?
|
962
|
+
error_block! Exception, boom
|
632
963
|
end
|
633
964
|
|
634
965
|
# Find an custom error block for the key(s) specified.
|
635
|
-
def error_block!(*
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
return instance_eval(&block)
|
642
|
-
else
|
643
|
-
base = base.superclass
|
644
|
-
end
|
645
|
-
end
|
966
|
+
def error_block!(key, *block_params)
|
967
|
+
base = settings
|
968
|
+
while base.respond_to?(:errors)
|
969
|
+
next base = base.superclass unless args = base.errors[key]
|
970
|
+
args += [block_params]
|
971
|
+
return process_route(*args)
|
646
972
|
end
|
647
|
-
|
973
|
+
return false unless key.respond_to? :superclass and key.superclass < Exception
|
974
|
+
error_block!(key.superclass, *block_params)
|
648
975
|
end
|
649
976
|
|
650
977
|
def dump_errors!(boom)
|
651
|
-
msg = ["#{boom.class} - #{boom.message}:",
|
652
|
-
*boom.backtrace].join("\n ")
|
978
|
+
msg = ["#{boom.class} - #{boom.message}:", *boom.backtrace].join("\n\t")
|
653
979
|
@env['rack.errors'].puts(msg)
|
654
980
|
end
|
655
981
|
|
656
982
|
class << self
|
657
|
-
attr_reader :routes, :
|
983
|
+
attr_reader :routes, :filters, :templates, :errors
|
658
984
|
|
985
|
+
# Removes all routes, filters, middleware and extension hooks from the
|
986
|
+
# current class (not routes/filters/... defined by its superclass).
|
659
987
|
def reset!
|
660
988
|
@conditions = []
|
661
989
|
@routes = {}
|
662
|
-
@
|
663
|
-
@after_filters = []
|
990
|
+
@filters = {:before => [], :after => []}
|
664
991
|
@errors = {}
|
665
992
|
@middleware = []
|
666
993
|
@prototype = nil
|
@@ -693,19 +1020,43 @@ module Sinatra
|
|
693
1020
|
|
694
1021
|
# Sets an option to the given value. If the value is a proc,
|
695
1022
|
# the proc will be called every time the option is accessed.
|
696
|
-
def set(option, value=
|
697
|
-
raise ArgumentError if block
|
698
|
-
value = block if block
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
1023
|
+
def set(option, value = (not_set = true), ignore_setter = false, &block)
|
1024
|
+
raise ArgumentError if block and !not_set
|
1025
|
+
value, not_set = block, false if block
|
1026
|
+
|
1027
|
+
if not_set
|
1028
|
+
raise ArgumentError unless option.respond_to?(:each)
|
1029
|
+
option.each { |k,v| set(k, v) }
|
1030
|
+
return self
|
1031
|
+
end
|
1032
|
+
|
1033
|
+
if respond_to?("#{option}=") and not ignore_setter
|
1034
|
+
return __send__("#{option}=", value)
|
1035
|
+
end
|
1036
|
+
|
1037
|
+
setter = proc { |val| set option, val, true }
|
1038
|
+
getter = proc { value }
|
1039
|
+
|
1040
|
+
case value
|
1041
|
+
when Proc
|
1042
|
+
getter = value
|
1043
|
+
when Symbol, Fixnum, FalseClass, TrueClass, NilClass
|
1044
|
+
# we have a lot of enable and disable calls, let's optimize those
|
1045
|
+
class_eval "def self.#{option}() #{value.inspect} end"
|
1046
|
+
getter = nil
|
1047
|
+
when Hash
|
1048
|
+
setter = proc do |val|
|
1049
|
+
val = value.merge val if Hash === val
|
1050
|
+
set option, val, true
|
1051
|
+
end
|
1052
|
+
end
|
1053
|
+
|
1054
|
+
(class << self; self; end).class_eval do
|
1055
|
+
define_method("#{option}=", &setter) if setter
|
1056
|
+
define_method(option, &getter) if getter
|
1057
|
+
unless method_defined? "#{option}?"
|
1058
|
+
class_eval "def #{option}?() !!#{option} end"
|
1059
|
+
end
|
709
1060
|
end
|
710
1061
|
self
|
711
1062
|
end
|
@@ -723,8 +1074,11 @@ module Sinatra
|
|
723
1074
|
# Define a custom error handler. Optionally takes either an Exception
|
724
1075
|
# class, or an HTTP status code to specify which errors should be
|
725
1076
|
# handled.
|
726
|
-
def error(codes
|
727
|
-
|
1077
|
+
def error(*codes, &block)
|
1078
|
+
args = compile! "ERROR", //, block
|
1079
|
+
codes = codes.map { |c| Array(c) }.flatten
|
1080
|
+
codes << Exception if codes.empty?
|
1081
|
+
codes.each { |c| @errors[c] = args }
|
728
1082
|
end
|
729
1083
|
|
730
1084
|
# Sugar for `error(404) { ... }`
|
@@ -746,22 +1100,28 @@ module Sinatra
|
|
746
1100
|
# Load embeded templates from the file; uses the caller's __FILE__
|
747
1101
|
# when no file is specified.
|
748
1102
|
def inline_templates=(file=nil)
|
749
|
-
file = (file.nil? || file == true) ? caller_files.first : file
|
1103
|
+
file = (file.nil? || file == true) ? (caller_files.first || File.expand_path($0)) : file
|
750
1104
|
|
751
1105
|
begin
|
752
|
-
|
753
|
-
|
1106
|
+
io = ::IO.respond_to?(:binread) ? ::IO.binread(file) : ::IO.read(file)
|
1107
|
+
app, data = io.gsub("\r\n", "\n").split(/^__END__$/, 2)
|
754
1108
|
rescue Errno::ENOENT
|
755
1109
|
app, data = nil
|
756
1110
|
end
|
757
1111
|
|
758
1112
|
if data
|
1113
|
+
if app and app =~ /([^\n]*\n)?#[^\n]*coding: *(\S+)/m
|
1114
|
+
encoding = $2
|
1115
|
+
else
|
1116
|
+
encoding = settings.default_encoding
|
1117
|
+
end
|
759
1118
|
lines = app.count("\n") + 1
|
760
1119
|
template = nil
|
1120
|
+
force_encoding data, encoding
|
761
1121
|
data.each_line do |line|
|
762
1122
|
lines += 1
|
763
|
-
if line =~ /^@@\s*(
|
764
|
-
template = ''
|
1123
|
+
if line =~ /^@@\s*(.*\S)\s*$/
|
1124
|
+
template = force_encoding('', encoding)
|
765
1125
|
templates[$1.to_sym] = [template, file, lines]
|
766
1126
|
elsif template
|
767
1127
|
template << line
|
@@ -778,56 +1138,79 @@ module Sinatra
|
|
778
1138
|
Rack::Mime::MIME_TYPES[type] = value
|
779
1139
|
end
|
780
1140
|
|
1141
|
+
# provides all mime types matching type, including deprecated types:
|
1142
|
+
# mime_types :html # => ['text/html']
|
1143
|
+
# mime_types :js # => ['application/javascript', 'text/javascript']
|
1144
|
+
def mime_types(type)
|
1145
|
+
type = mime_type type
|
1146
|
+
type =~ /^application\/(xml|javascript)$/ ? [type, "text/#$1"] : [type]
|
1147
|
+
end
|
1148
|
+
|
781
1149
|
# Define a before filter; runs before all requests within the same
|
782
1150
|
# context as route handlers and may access/modify the request and
|
783
1151
|
# response.
|
784
|
-
def before(&block)
|
785
|
-
|
1152
|
+
def before(path = nil, options = {}, &block)
|
1153
|
+
add_filter(:before, path, options, &block)
|
786
1154
|
end
|
787
1155
|
|
788
1156
|
# Define an after filter; runs after all requests within the same
|
789
1157
|
# context as route handlers and may access/modify the request and
|
790
1158
|
# response.
|
791
|
-
def after(&block)
|
792
|
-
|
1159
|
+
def after(path = nil, options = {}, &block)
|
1160
|
+
add_filter(:after, path, options, &block)
|
1161
|
+
end
|
1162
|
+
|
1163
|
+
# add a filter
|
1164
|
+
def add_filter(type, path = nil, options = {}, &block)
|
1165
|
+
path, options = //, path if path.respond_to?(:each_pair)
|
1166
|
+
filters[type] << compile!(type, path || //, block, options)
|
793
1167
|
end
|
794
1168
|
|
795
1169
|
# Add a route condition. The route is considered non-matching when the
|
796
1170
|
# block returns false.
|
797
|
-
def condition(&block)
|
798
|
-
@conditions << block
|
1171
|
+
def condition(name = "#{caller.first[/`.*'/]} condition", &block)
|
1172
|
+
@conditions << generate_method(name, &block)
|
1173
|
+
end
|
1174
|
+
|
1175
|
+
def public=(value)
|
1176
|
+
warn ":public is no longer used to avoid overloading Module#public, use :public_folder instead"
|
1177
|
+
set(:public_folder, value)
|
799
1178
|
end
|
800
1179
|
|
801
1180
|
private
|
1181
|
+
# Condition for matching host name. Parameter might be String or Regexp.
|
802
1182
|
def host_name(pattern)
|
803
1183
|
condition { pattern === request.host }
|
804
1184
|
end
|
805
1185
|
|
1186
|
+
# Condition for matching user agent. Parameter should be Regexp.
|
1187
|
+
# Will set params[:agent].
|
806
1188
|
def user_agent(pattern)
|
807
|
-
condition
|
808
|
-
if request.user_agent =~ pattern
|
1189
|
+
condition do
|
1190
|
+
if request.user_agent.to_s =~ pattern
|
809
1191
|
@params[:agent] = $~[1..-1]
|
810
1192
|
true
|
811
1193
|
else
|
812
1194
|
false
|
813
1195
|
end
|
814
|
-
|
1196
|
+
end
|
815
1197
|
end
|
816
1198
|
alias_method :agent, :user_agent
|
817
1199
|
|
1200
|
+
# Condition for matching mimetypes. Accepts file extensions.
|
818
1201
|
def provides(*types)
|
819
|
-
types
|
820
|
-
types.
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
1202
|
+
types.map! { |t| mime_types(t) }
|
1203
|
+
types.flatten!
|
1204
|
+
condition do
|
1205
|
+
if type = response['Content-Type']
|
1206
|
+
types.include? type or types.include? type[/^[^;]+/]
|
1207
|
+
elsif type = request.preferred_type(types)
|
1208
|
+
content_type(type)
|
826
1209
|
true
|
827
1210
|
else
|
828
1211
|
false
|
829
1212
|
end
|
830
|
-
|
1213
|
+
end
|
831
1214
|
end
|
832
1215
|
|
833
1216
|
public
|
@@ -841,60 +1224,65 @@ module Sinatra
|
|
841
1224
|
route('HEAD', path, opts, &block)
|
842
1225
|
end
|
843
1226
|
|
844
|
-
def put(path, opts={}, &bk)
|
845
|
-
def post(path, opts={}, &bk)
|
846
|
-
def delete(path, opts={}, &bk)
|
847
|
-
def head(path, opts={}, &bk)
|
1227
|
+
def put(path, opts={}, &bk) route 'PUT', path, opts, &bk end
|
1228
|
+
def post(path, opts={}, &bk) route 'POST', path, opts, &bk end
|
1229
|
+
def delete(path, opts={}, &bk) route 'DELETE', path, opts, &bk end
|
1230
|
+
def head(path, opts={}, &bk) route 'HEAD', path, opts, &bk end
|
1231
|
+
def options(path, opts={}, &bk) route 'OPTIONS', path, opts, &bk end
|
1232
|
+
def patch(path, opts={}, &bk) route 'PATCH', path, opts, &bk end
|
848
1233
|
|
849
1234
|
private
|
850
1235
|
def route(verb, path, options={}, &block)
|
851
1236
|
# Because of self.options.host
|
852
|
-
host_name(options.delete(:
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
pattern, keys = compile(path)
|
857
|
-
conditions, @conditions = @conditions, []
|
858
|
-
|
859
|
-
define_method "#{verb} #{path}", &block
|
860
|
-
unbound_method = instance_method("#{verb} #{path}")
|
861
|
-
block =
|
862
|
-
if block.arity != 0
|
863
|
-
proc { unbound_method.bind(self).call(*@block_params) }
|
864
|
-
else
|
865
|
-
proc { unbound_method.bind(self).call }
|
866
|
-
end
|
867
|
-
|
1237
|
+
host_name(options.delete(:host)) if options.key?(:host)
|
1238
|
+
enable :empty_path_info if path == "" and empty_path_info.nil?
|
1239
|
+
signature = compile!(verb, path, block, options)
|
1240
|
+
(@routes[verb] ||= []) << signature
|
868
1241
|
invoke_hook(:route_added, verb, path, block)
|
869
|
-
|
870
|
-
(@routes[verb] ||= []).
|
871
|
-
push([pattern, keys, conditions, block]).last
|
1242
|
+
signature
|
872
1243
|
end
|
873
1244
|
|
874
1245
|
def invoke_hook(name, *args)
|
875
1246
|
extensions.each { |e| e.send(name, *args) if e.respond_to?(name) }
|
876
1247
|
end
|
877
1248
|
|
1249
|
+
def generate_method(method_name, &block)
|
1250
|
+
define_method(method_name, &block)
|
1251
|
+
method = instance_method method_name
|
1252
|
+
remove_method method_name
|
1253
|
+
method
|
1254
|
+
end
|
1255
|
+
|
1256
|
+
def compile!(verb, path, block, options = {})
|
1257
|
+
options.each_pair { |option, args| send(option, *args) }
|
1258
|
+
method_name = "#{verb} #{path}"
|
1259
|
+
unbound_method = generate_method(method_name, &block)
|
1260
|
+
pattern, keys = compile path
|
1261
|
+
conditions, @conditions = @conditions, []
|
1262
|
+
|
1263
|
+
[ pattern, keys, conditions, block.arity != 0 ?
|
1264
|
+
proc { |a,p| unbound_method.bind(a).call(*p) } :
|
1265
|
+
proc { |a,p| unbound_method.bind(a).call } ]
|
1266
|
+
end
|
1267
|
+
|
878
1268
|
def compile(path)
|
879
1269
|
keys = []
|
880
1270
|
if path.respond_to? :to_str
|
881
|
-
|
882
|
-
pattern
|
883
|
-
|
884
|
-
|
885
|
-
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
Regexp.escape(match)
|
890
|
-
else
|
891
|
-
keys << $2[1..-1]
|
892
|
-
"([^/?&#]+)"
|
893
|
-
end
|
1271
|
+
pattern = path.to_str.gsub(/[^\?\%\\\/\:\*\w]/) { |c| encoded(c) }
|
1272
|
+
pattern.gsub!(/((:\w+)|\*)/) do |match|
|
1273
|
+
if match == "*"
|
1274
|
+
keys << 'splat'
|
1275
|
+
"(.*?)"
|
1276
|
+
else
|
1277
|
+
keys << $2[1..-1]
|
1278
|
+
"([^/?#]+)"
|
894
1279
|
end
|
1280
|
+
end
|
895
1281
|
[/^#{pattern}$/, keys]
|
896
1282
|
elsif path.respond_to?(:keys) && path.respond_to?(:match)
|
897
1283
|
[path, path.keys]
|
1284
|
+
elsif path.respond_to?(:names) && path.respond_to?(:match)
|
1285
|
+
[path, path.names]
|
898
1286
|
elsif path.respond_to? :match
|
899
1287
|
[path, keys]
|
900
1288
|
else
|
@@ -902,14 +1290,23 @@ module Sinatra
|
|
902
1290
|
end
|
903
1291
|
end
|
904
1292
|
|
1293
|
+
def encoded(char)
|
1294
|
+
enc = URI.encode(char)
|
1295
|
+
enc = "(?:#{Regexp.escape enc}|#{URI.encode char, /./})" if enc == char
|
1296
|
+
enc = "(?:#{enc}|#{encoded('+')})" if char == " "
|
1297
|
+
enc
|
1298
|
+
end
|
1299
|
+
|
905
1300
|
public
|
906
1301
|
# Makes the methods defined in the block and in the Modules given
|
907
1302
|
# in `extensions` available to the handlers and templates
|
908
1303
|
def helpers(*extensions, &block)
|
909
|
-
class_eval(&block)
|
1304
|
+
class_eval(&block) if block_given?
|
910
1305
|
include(*extensions) if extensions.any?
|
911
1306
|
end
|
912
1307
|
|
1308
|
+
# Register an extension. Alternatively take a block from which an
|
1309
|
+
# extension will be created and registered on the fly.
|
913
1310
|
def register(*extensions, &block)
|
914
1311
|
extensions << Module.new(&block) if block_given?
|
915
1312
|
@extensions += extensions
|
@@ -935,24 +1332,31 @@ module Sinatra
|
|
935
1332
|
@middleware << [middleware, args, block]
|
936
1333
|
end
|
937
1334
|
|
1335
|
+
def quit!(server, handler_name)
|
1336
|
+
# Use Thin's hard #stop! if available, otherwise just #stop.
|
1337
|
+
server.respond_to?(:stop!) ? server.stop! : server.stop
|
1338
|
+
$stderr.puts "\n== Sinatra has ended his set (crowd applauds)" unless handler_name =~/cgi/i
|
1339
|
+
end
|
1340
|
+
|
938
1341
|
# Run the Sinatra app as a self-hosted server using
|
939
|
-
# Thin, Mongrel or WEBrick (in that order)
|
1342
|
+
# Thin, Mongrel or WEBrick (in that order). If given a block, will call
|
1343
|
+
# with the constructed handler once we have taken the stage.
|
940
1344
|
def run!(options={})
|
941
1345
|
set options
|
942
1346
|
handler = detect_rack_handler
|
943
1347
|
handler_name = handler.name.gsub(/.*::/, '')
|
944
|
-
puts "== Sinatra/#{Sinatra::VERSION} has taken the stage " +
|
945
|
-
"on #{port} for #{environment} with backup from #{handler_name}" unless handler_name =~/cgi/i
|
946
1348
|
handler.run self, :Host => bind, :Port => port do |server|
|
947
|
-
|
948
|
-
|
949
|
-
|
950
|
-
puts "\n== Sinatra has ended his set (crowd applauds)" unless handler_name =~/cgi/i
|
1349
|
+
unless handler_name =~ /cgi/i
|
1350
|
+
$stderr.puts "== Sinatra/#{Sinatra::VERSION} has taken the stage " +
|
1351
|
+
"on #{port} for #{environment} with backup from #{handler_name}"
|
951
1352
|
end
|
1353
|
+
[:INT, :TERM].each { |sig| trap(sig) { quit!(server, handler_name) } }
|
1354
|
+
server.threaded = settings.threaded if server.respond_to? :threaded=
|
952
1355
|
set :running, true
|
1356
|
+
yield server if block_given?
|
953
1357
|
end
|
954
|
-
rescue Errno::EADDRINUSE
|
955
|
-
puts "== Someone is already performing on port #{port}!"
|
1358
|
+
rescue Errno::EADDRINUSE
|
1359
|
+
$stderr.puts "== Someone is already performing on port #{port}!"
|
956
1360
|
end
|
957
1361
|
|
958
1362
|
# The prototype instance used to process requests.
|
@@ -960,19 +1364,23 @@ module Sinatra
|
|
960
1364
|
@prototype ||= new
|
961
1365
|
end
|
962
1366
|
|
1367
|
+
# Create a new instance without middleware in front of it.
|
1368
|
+
alias new! new unless method_defined? :new!
|
1369
|
+
|
963
1370
|
# Create a new instance of the class fronted by its middleware
|
964
1371
|
# pipeline. The object is guaranteed to respond to #call but may not be
|
965
1372
|
# an instance of the class new was called on.
|
966
1373
|
def new(*args, &bk)
|
967
|
-
|
968
|
-
|
969
|
-
builder.use Rack::CommonLogger if logging?
|
970
|
-
builder.use Rack::MethodOverride if method_override?
|
971
|
-
builder.use ShowExceptions if show_exceptions?
|
972
|
-
middleware.each { |c,a,b| builder.use(c, *a, &b) }
|
1374
|
+
build(Rack::Builder.new, *args, &bk).to_app
|
1375
|
+
end
|
973
1376
|
|
974
|
-
|
975
|
-
|
1377
|
+
# Creates a Rack::Builder instance with all the middleware set up and
|
1378
|
+
# an instance of this class as end point.
|
1379
|
+
def build(builder, *args, &bk)
|
1380
|
+
setup_default_middleware builder
|
1381
|
+
setup_middleware builder
|
1382
|
+
builder.run new!(*args, &bk)
|
1383
|
+
builder
|
976
1384
|
end
|
977
1385
|
|
978
1386
|
def call(env)
|
@@ -980,13 +1388,66 @@ module Sinatra
|
|
980
1388
|
end
|
981
1389
|
|
982
1390
|
private
|
1391
|
+
def setup_default_middleware(builder)
|
1392
|
+
builder.use ShowExceptions if show_exceptions?
|
1393
|
+
builder.use Rack::MethodOverride if method_override?
|
1394
|
+
builder.use Rack::Head
|
1395
|
+
setup_logging builder
|
1396
|
+
setup_sessions builder
|
1397
|
+
setup_protection builder
|
1398
|
+
end
|
1399
|
+
|
1400
|
+
def setup_middleware(builder)
|
1401
|
+
middleware.each { |c,a,b| builder.use(c, *a, &b) }
|
1402
|
+
end
|
1403
|
+
|
1404
|
+
def setup_logging(builder)
|
1405
|
+
if logging?
|
1406
|
+
setup_common_logger(builder)
|
1407
|
+
setup_custom_logger(builder)
|
1408
|
+
elsif logging == false
|
1409
|
+
setup_null_logger(builder)
|
1410
|
+
end
|
1411
|
+
end
|
1412
|
+
|
1413
|
+
def setup_null_logger(builder)
|
1414
|
+
builder.use Rack::NullLogger
|
1415
|
+
end
|
1416
|
+
|
1417
|
+
def setup_common_logger(builder)
|
1418
|
+
builder.use Sinatra::CommonLogger
|
1419
|
+
end
|
1420
|
+
|
1421
|
+
def setup_custom_logger(builder)
|
1422
|
+
if logging.respond_to? :to_int
|
1423
|
+
builder.use Rack::Logger, logging
|
1424
|
+
else
|
1425
|
+
builder.use Rack::Logger
|
1426
|
+
end
|
1427
|
+
end
|
1428
|
+
|
1429
|
+
def setup_protection(builder)
|
1430
|
+
return unless protection?
|
1431
|
+
options = Hash === protection ? protection.dup : {}
|
1432
|
+
options[:except] = Array options[:except]
|
1433
|
+
options[:except] += [:session_hijacking, :remote_token] unless sessions?
|
1434
|
+
builder.use Rack::Protection, options
|
1435
|
+
end
|
1436
|
+
|
1437
|
+
def setup_sessions(builder)
|
1438
|
+
return unless sessions?
|
1439
|
+
options = {}
|
1440
|
+
options[:secret] = session_secret if session_secret?
|
1441
|
+
options.merge! sessions.to_hash if sessions.respond_to? :to_hash
|
1442
|
+
builder.use Rack::Session::Cookie, options
|
1443
|
+
end
|
1444
|
+
|
983
1445
|
def detect_rack_handler
|
984
|
-
servers = Array(
|
1446
|
+
servers = Array(server)
|
985
1447
|
servers.each do |server_name|
|
986
1448
|
begin
|
987
|
-
return Rack::Handler.get(server_name.
|
988
|
-
rescue LoadError
|
989
|
-
rescue NameError
|
1449
|
+
return Rack::Handler.get(server_name.to_s)
|
1450
|
+
rescue LoadError, NameError
|
990
1451
|
end
|
991
1452
|
end
|
992
1453
|
fail "Server handler (#{servers.join(',')}) not found."
|
@@ -994,6 +1455,7 @@ module Sinatra
|
|
994
1455
|
|
995
1456
|
def inherited(subclass)
|
996
1457
|
subclass.reset!
|
1458
|
+
subclass.set :app_file, caller_files.first unless subclass.app_file?
|
997
1459
|
super
|
998
1460
|
end
|
999
1461
|
|
@@ -1006,37 +1468,73 @@ module Sinatra
|
|
1006
1468
|
end
|
1007
1469
|
end
|
1008
1470
|
|
1009
|
-
def metadef(message, &block)
|
1010
|
-
(class << self; self; end).
|
1011
|
-
send :define_method, message, &block
|
1012
|
-
end
|
1013
|
-
|
1014
1471
|
public
|
1015
|
-
CALLERS_TO_IGNORE = [
|
1472
|
+
CALLERS_TO_IGNORE = [ # :nodoc:
|
1016
1473
|
/\/sinatra(\/(base|main|showexceptions))?\.rb$/, # all sinatra code
|
1017
|
-
/lib\/tilt.*\.rb$/,
|
1018
|
-
|
1019
|
-
/custom_require\.rb$/,
|
1020
|
-
/active_support/,
|
1474
|
+
/lib\/tilt.*\.rb$/, # all tilt code
|
1475
|
+
/^\(.*\)$/, # generated code
|
1476
|
+
/rubygems\/custom_require\.rb$/, # rubygems require hacks
|
1477
|
+
/active_support/, # active_support require hacks
|
1478
|
+
/bundler(\/runtime)?\.rb/, # bundler require hacks
|
1479
|
+
/<internal:/, # internal in ruby >= 1.9.2
|
1480
|
+
/src\/kernel\/bootstrap\/[A-Z]/ # maglev kernel files
|
1021
1481
|
]
|
1022
1482
|
|
1023
|
-
#
|
1024
|
-
|
1483
|
+
# contrary to what the comment said previously, rubinius never supported this
|
1484
|
+
if defined?(RUBY_IGNORE_CALLERS)
|
1485
|
+
warn "RUBY_IGNORE_CALLERS is deprecated and will no longer be supported by Sinatra 2.0"
|
1486
|
+
CALLERS_TO_IGNORE.concat(RUBY_IGNORE_CALLERS)
|
1487
|
+
end
|
1025
1488
|
|
1026
1489
|
# Like Kernel#caller but excluding certain magic entries and without
|
1027
1490
|
# line / method information; the resulting array contains filenames only.
|
1028
1491
|
def caller_files
|
1029
|
-
|
1030
|
-
map { |file,line| file }
|
1492
|
+
cleaned_caller(1).flatten
|
1031
1493
|
end
|
1032
1494
|
|
1495
|
+
# Like caller_files, but containing Arrays rather than strings with the
|
1496
|
+
# first element being the file, and the second being the line.
|
1033
1497
|
def caller_locations
|
1498
|
+
cleaned_caller 2
|
1499
|
+
end
|
1500
|
+
|
1501
|
+
private
|
1502
|
+
# used for deprecation warnings
|
1503
|
+
def warn(message)
|
1504
|
+
super message + "\n\tfrom #{cleaned_caller.first.join(':')}"
|
1505
|
+
end
|
1506
|
+
|
1507
|
+
# Like Kernel#caller but excluding certain magic entries
|
1508
|
+
def cleaned_caller(keep = 3)
|
1034
1509
|
caller(1).
|
1035
|
-
map { |line| line.split(/:(?=\d|in )
|
1036
|
-
reject { |file,
|
1510
|
+
map { |line| line.split(/:(?=\d|in )/, 3)[0,keep] }.
|
1511
|
+
reject { |file, *_| CALLERS_TO_IGNORE.any? { |pattern| file =~ pattern } }
|
1037
1512
|
end
|
1038
1513
|
end
|
1039
1514
|
|
1515
|
+
# Fixes encoding issues by
|
1516
|
+
# * defaulting to UTF-8
|
1517
|
+
# * casting params to Encoding.default_external
|
1518
|
+
#
|
1519
|
+
# The latter might not be necessary if Rack handles it one day.
|
1520
|
+
# Keep an eye on Rack's LH #100.
|
1521
|
+
def force_encoding(*args) settings.force_encoding(*args) end
|
1522
|
+
if defined? Encoding
|
1523
|
+
def self.force_encoding(data, encoding = default_encoding)
|
1524
|
+
return if data == settings || data.is_a?(Tempfile)
|
1525
|
+
if data.respond_to? :force_encoding
|
1526
|
+
data.force_encoding(encoding).encode!
|
1527
|
+
elsif data.respond_to? :each_value
|
1528
|
+
data.each_value { |v| force_encoding(v, encoding) }
|
1529
|
+
elsif data.respond_to? :each
|
1530
|
+
data.each { |v| force_encoding(v, encoding) }
|
1531
|
+
end
|
1532
|
+
data
|
1533
|
+
end
|
1534
|
+
else
|
1535
|
+
def self.force_encoding(data, *) data end
|
1536
|
+
end
|
1537
|
+
|
1040
1538
|
reset!
|
1041
1539
|
|
1042
1540
|
set :environment, (ENV['RACK_ENV'] || :development).to_sym
|
@@ -1045,7 +1543,21 @@ module Sinatra
|
|
1045
1543
|
set :show_exceptions, Proc.new { development? }
|
1046
1544
|
set :sessions, false
|
1047
1545
|
set :logging, false
|
1546
|
+
set :protection, true
|
1048
1547
|
set :method_override, false
|
1548
|
+
set :use_code, true
|
1549
|
+
set :default_encoding, "utf-8"
|
1550
|
+
set :add_charset, %w[javascript xml xhtml+xml json].map { |t| "application/#{t}" }
|
1551
|
+
settings.add_charset << /^text\//
|
1552
|
+
|
1553
|
+
# explicitly generating a session secret eagerly to play nice with preforking
|
1554
|
+
begin
|
1555
|
+
require 'securerandom'
|
1556
|
+
set :session_secret, SecureRandom.hex(64)
|
1557
|
+
rescue LoadError, NotImplementedError
|
1558
|
+
# SecureRandom raises a NotImplementedError if no random device is available
|
1559
|
+
set :session_secret, "%064x" % Kernel.rand(2**256-1)
|
1560
|
+
end
|
1049
1561
|
|
1050
1562
|
class << self
|
1051
1563
|
alias_method :methodoverride?, :method_override?
|
@@ -1054,18 +1566,24 @@ module Sinatra
|
|
1054
1566
|
|
1055
1567
|
set :run, false # start server via at-exit hook?
|
1056
1568
|
set :running, false # is the built-in server running now?
|
1057
|
-
set :server, %w[thin mongrel webrick]
|
1569
|
+
set :server, %w[thin puma mongrel webrick]
|
1058
1570
|
set :bind, '0.0.0.0'
|
1059
1571
|
set :port, 4567
|
1060
1572
|
|
1573
|
+
set :absolute_redirects, true
|
1574
|
+
set :prefixed_redirects, false
|
1575
|
+
set :empty_path_info, nil
|
1576
|
+
|
1061
1577
|
set :app_file, nil
|
1062
1578
|
set :root, Proc.new { app_file && File.expand_path(File.dirname(app_file)) }
|
1063
1579
|
set :views, Proc.new { root && File.join(root, 'views') }
|
1064
1580
|
set :reload_templates, Proc.new { development? }
|
1065
1581
|
set :lock, false
|
1582
|
+
set :threaded, true
|
1066
1583
|
|
1067
|
-
set :
|
1068
|
-
set :static, Proc.new {
|
1584
|
+
set :public_folder, Proc.new { root && File.join(root, 'public') }
|
1585
|
+
set :static, Proc.new { public_folder && File.exist?(public_folder) }
|
1586
|
+
set :static_cache_control, false
|
1069
1587
|
|
1070
1588
|
error ::Exception do
|
1071
1589
|
response.status = 500
|
@@ -1094,8 +1612,8 @@ module Sinatra
|
|
1094
1612
|
</style>
|
1095
1613
|
</head>
|
1096
1614
|
<body>
|
1097
|
-
<h2>Sinatra doesn
|
1098
|
-
<img src='/__sinatra__/404.png'>
|
1615
|
+
<h2>Sinatra doesn’t know this ditty.</h2>
|
1616
|
+
<img src='#{uri "/__sinatra__/404.png"}'>
|
1099
1617
|
<div id="c">
|
1100
1618
|
Try this:
|
1101
1619
|
<pre>#{request.request_method.downcase} '#{request.path_info}' do\n "Hello World"\nend</pre>
|
@@ -1112,12 +1630,14 @@ module Sinatra
|
|
1112
1630
|
#
|
1113
1631
|
# The Application class should not be subclassed, unless you want to
|
1114
1632
|
# inherit all settings, routes, handlers, and error pages from the
|
1115
|
-
# top-level. Subclassing Sinatra::Base is
|
1633
|
+
# top-level. Subclassing Sinatra::Base is highly recommended for
|
1116
1634
|
# modular applications.
|
1117
1635
|
class Application < Base
|
1118
1636
|
set :logging, Proc.new { ! test? }
|
1119
1637
|
set :method_override, true
|
1120
1638
|
set :run, Proc.new { ! test? }
|
1639
|
+
set :session_secret, Proc.new { super() unless development? }
|
1640
|
+
set :app_file, nil
|
1121
1641
|
|
1122
1642
|
def self.register(*extensions, &block) #:nodoc:
|
1123
1643
|
added_methods = extensions.map {|m| m.public_instance_methods }.flatten
|
@@ -1132,36 +1652,46 @@ module Sinatra
|
|
1132
1652
|
module Delegator #:nodoc:
|
1133
1653
|
def self.delegate(*methods)
|
1134
1654
|
methods.each do |method_name|
|
1135
|
-
|
1136
|
-
|
1137
|
-
|
1138
|
-
|
1139
|
-
|
1140
|
-
RUBY
|
1655
|
+
define_method(method_name) do |*args, &block|
|
1656
|
+
return super(*args, &block) if respond_to? method_name
|
1657
|
+
Delegator.target.send(method_name, *args, &block)
|
1658
|
+
end
|
1659
|
+
private method_name
|
1141
1660
|
end
|
1142
1661
|
end
|
1143
1662
|
|
1144
|
-
delegate :get, :put, :post, :delete, :head, :template, :layout,
|
1663
|
+
delegate :get, :patch, :put, :post, :delete, :head, :options, :template, :layout,
|
1145
1664
|
:before, :after, :error, :not_found, :configure, :set, :mime_type,
|
1146
1665
|
:enable, :disable, :use, :development?, :test?, :production?,
|
1147
1666
|
:helpers, :settings
|
1667
|
+
|
1668
|
+
class << self
|
1669
|
+
attr_accessor :target
|
1670
|
+
end
|
1671
|
+
|
1672
|
+
self.target = Application
|
1148
1673
|
end
|
1149
1674
|
|
1150
1675
|
# Create a new Sinatra application. The block is evaluated in the new app's
|
1151
1676
|
# class scope.
|
1152
1677
|
def self.new(base=Base, options={}, &block)
|
1153
1678
|
base = Class.new(base)
|
1154
|
-
base.
|
1679
|
+
base.class_eval(&block) if block_given?
|
1155
1680
|
base
|
1156
1681
|
end
|
1157
1682
|
|
1158
1683
|
# Extend the top-level DSL with the modules provided.
|
1159
1684
|
def self.register(*extensions, &block)
|
1160
|
-
|
1685
|
+
Delegator.target.register(*extensions, &block)
|
1161
1686
|
end
|
1162
1687
|
|
1163
1688
|
# Include the helper modules provided in Sinatra's request context.
|
1164
1689
|
def self.helpers(*extensions, &block)
|
1165
|
-
|
1690
|
+
Delegator.target.helpers(*extensions, &block)
|
1691
|
+
end
|
1692
|
+
|
1693
|
+
# Use the middleware for classic applications.
|
1694
|
+
def self.use(*args, &block)
|
1695
|
+
Delegator.target.use(*args, &block)
|
1166
1696
|
end
|
1167
1697
|
end
|