sinatra 0.1.7 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of sinatra might be problematic. Click here for more details.
- data/CHANGELOG +1 -8
- data/Manifest +42 -49
- data/README.rdoc +430 -0
- data/Rakefile +22 -28
- data/images/404.png +0 -0
- data/images/500.png +0 -0
- data/index.html +9 -0
- data/lib/sinatra.rb +1144 -46
- data/lib/sinatra/test/methods.rb +56 -0
- data/lib/sinatra/test/spec.rb +10 -0
- data/lib/sinatra/test/unit.rb +13 -0
- data/sinatra.gemspec +44 -40
- data/test/app_test.rb +150 -0
- data/test/application_test.rb +175 -0
- data/test/builder_test.rb +101 -0
- data/test/custom_error_test.rb +67 -0
- data/test/diddy_test.rb +41 -0
- data/test/erb_test.rb +116 -0
- data/test/event_context_test.rb +15 -0
- data/test/events_test.rb +50 -0
- data/test/haml_test.rb +181 -0
- data/test/helper.rb +3 -16
- data/test/mapped_error_test.rb +61 -0
- data/test/public/foo.xml +1 -0
- data/test/rest_test.rb +16 -0
- data/test/sass_test.rb +57 -0
- data/test/sessions_test.rb +40 -0
- data/test/streaming_test.rb +112 -0
- data/test/sym_params_test.rb +19 -0
- data/test/template_test.rb +30 -0
- data/test/use_in_file_templates_test.rb +48 -0
- data/test/views/foo.builder +1 -0
- data/test/views/foo.erb +1 -0
- data/test/views/foo.haml +1 -0
- data/test/views/foo.sass +2 -0
- data/test/views/foo_layout.erb +2 -0
- data/test/views/foo_layout.haml +2 -0
- data/test/views/layout_test/foo.builder +1 -0
- data/test/views/layout_test/foo.erb +1 -0
- data/test/views/layout_test/foo.haml +1 -0
- data/test/views/layout_test/foo.sass +2 -0
- data/test/views/layout_test/layout.builder +3 -0
- data/test/views/layout_test/layout.erb +1 -0
- data/test/views/layout_test/layout.haml +1 -0
- data/test/views/layout_test/layout.sass +2 -0
- data/test/views/no_layout/no_layout.builder +1 -0
- data/test/views/no_layout/no_layout.haml +1 -0
- metadata +122 -98
- data/LICENSE +0 -22
- data/README +0 -100
- data/RakeFile +0 -35
- data/examples/hello/hello.rb +0 -28
- data/examples/hello/views/hello.erb +0 -1
- data/examples/todo/todo.rb +0 -38
- data/files/default_index.erb +0 -42
- data/files/error.erb +0 -9
- data/files/logo.png +0 -0
- data/files/not_found.erb +0 -52
- data/lib/sinatra/context.rb +0 -88
- data/lib/sinatra/context/renderer.rb +0 -75
- data/lib/sinatra/core_ext/array.rb +0 -5
- data/lib/sinatra/core_ext/class.rb +0 -49
- data/lib/sinatra/core_ext/hash.rb +0 -7
- data/lib/sinatra/core_ext/kernel.rb +0 -16
- data/lib/sinatra/core_ext/metaid.rb +0 -18
- data/lib/sinatra/core_ext/module.rb +0 -11
- data/lib/sinatra/core_ext/symbol.rb +0 -5
- data/lib/sinatra/dispatcher.rb +0 -27
- data/lib/sinatra/dsl.rb +0 -176
- data/lib/sinatra/environment.rb +0 -15
- data/lib/sinatra/event.rb +0 -238
- data/lib/sinatra/irb.rb +0 -56
- data/lib/sinatra/loader.rb +0 -31
- data/lib/sinatra/logger.rb +0 -22
- data/lib/sinatra/options.rb +0 -49
- data/lib/sinatra/rack_ext/request.rb +0 -15
- data/lib/sinatra/route.rb +0 -65
- data/lib/sinatra/server.rb +0 -57
- data/lib/sinatra/sessions.rb +0 -21
- data/lib/sinatra/test_methods.rb +0 -55
- data/site/index.htm +0 -104
- data/site/index.html +0 -104
- data/site/logo.png +0 -0
- data/test/sinatra/dispatcher_test.rb +0 -91
- data/test/sinatra/event_test.rb +0 -46
- data/test/sinatra/renderer_test.rb +0 -47
- data/test/sinatra/request_test.rb +0 -21
- data/test/sinatra/route_test.rb +0 -21
- data/test/sinatra/static_files/foo.txt +0 -1
- data/test/sinatra/static_files_test.rb +0 -48
- data/test/sinatra/url_test.rb +0 -18
- data/vendor/erb/init.rb +0 -3
- data/vendor/erb/lib/erb.rb +0 -41
- data/vendor/haml/init.rb +0 -3
- data/vendor/haml/lib/haml.rb +0 -41
data/Rakefile
CHANGED
@@ -1,35 +1,29 @@
|
|
1
|
+
require 'rubygems'
|
1
2
|
require 'rake/testtask'
|
2
|
-
require '
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
require 'echoe'
|
3
5
|
|
4
|
-
|
6
|
+
task :default => :test
|
5
7
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
Echoe.new('sinatra') do |p|
|
13
|
-
p.rubyforge_name = 'sinatra'
|
14
|
-
p.dependencies = ['mongrel >=1.0.1', 'rack >=0.2.0']
|
15
|
-
p.summary = "Sinatra is a classy web-framework dressed in a DSL"
|
16
|
-
p.description = "Sinatra is a classy web-framework dressed in a DSL"
|
17
|
-
p.url = "http://sinatra.rubyforge.org/"
|
18
|
-
p.author = 'Blake Mizerany'
|
19
|
-
p.email = "blake.mizerany@gmail.com"
|
20
|
-
p.test_pattern = 'test/**/*_test.rb'
|
21
|
-
p.include_rakefile = true
|
22
|
-
p.rdoc_pattern = ['README', 'LICENSE'] + Dir.glob('lib/**/*.rb') + Dir.glob('vendor/**/*.rb')
|
23
|
-
p.docs_host = "bmizerany@rubyforge.org:/var/www/gforge-projects/"
|
24
|
-
end
|
8
|
+
Rake::RDocTask.new do |rd|
|
9
|
+
rd.main = "README.rdoc"
|
10
|
+
rd.rdoc_files += ["README.rdoc"]
|
11
|
+
rd.rdoc_files += Dir.glob("lib/**/*.rb")
|
12
|
+
rd.rdoc_dir = 'doc'
|
13
|
+
end
|
25
14
|
|
26
|
-
|
15
|
+
Rake::TestTask.new do |t|
|
16
|
+
ENV['SINATRA_ENV'] = 'test'
|
17
|
+
t.pattern = File.dirname(__FILE__) + "/test/*_test.rb"
|
27
18
|
end
|
28
19
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
20
|
+
Echoe.new("sinatra") do |p|
|
21
|
+
p.author = "Blake Mizerany"
|
22
|
+
p.summary = "Classy web-development dressed in a DSL"
|
23
|
+
p.url = "http://www.sinatrarb.com"
|
24
|
+
p.docs_host = "sinatrarb.com:/var/www/blakemizerany.com/public/docs/"
|
25
|
+
p.dependencies = ["mongrel >=1.0.1", "rack >= 0.3.0"]
|
26
|
+
p.install_message = "*** Be sure to checkout the site for helpful tips! sinatrarb.com ***"
|
27
|
+
p.include_rakefile = true
|
35
28
|
end
|
29
|
+
|
data/images/404.png
ADDED
Binary file
|
data/images/500.png
ADDED
Binary file
|
data/index.html
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Your Page Title</title>
|
5
|
+
<meta http-equiv="REFRESH" content="0;url=http://sinatrarb.com"></HEAD>
|
6
|
+
<BODY>
|
7
|
+
This site has <a href="http:://sinatrarb.com">moved</a>.
|
8
|
+
</BODY>
|
9
|
+
</HTML>
|
data/lib/sinatra.rb
CHANGED
@@ -1,49 +1,1147 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
#
|
21
|
-
#
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
1
|
+
require 'rubygems'
|
2
|
+
require 'uri'
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
if ENV['SWIFT']
|
6
|
+
require 'swiftcore/swiftiplied_mongrel'
|
7
|
+
puts "Using Swiftiplied Mongrel"
|
8
|
+
elsif ENV['EVENT']
|
9
|
+
require 'swiftcore/evented_mongrel'
|
10
|
+
puts "Using Evented Mongrel"
|
11
|
+
end
|
12
|
+
|
13
|
+
require 'rack'
|
14
|
+
require 'ostruct'
|
15
|
+
|
16
|
+
class Class
|
17
|
+
def dslify_writer(*syms)
|
18
|
+
syms.each do |sym|
|
19
|
+
class_eval <<-end_eval
|
20
|
+
def #{sym}(v=nil)
|
21
|
+
self.send "#{sym}=", v if v
|
22
|
+
v
|
23
|
+
end
|
24
|
+
end_eval
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
module Rack #:nodoc:
|
30
|
+
|
31
|
+
class Request #:nodoc:
|
32
|
+
|
33
|
+
# Set of request method names allowed via the _method parameter hack. By default,
|
34
|
+
# all request methods defined in RFC2616 are included, with the exception of
|
35
|
+
# TRACE and CONNECT.
|
36
|
+
POST_TUNNEL_METHODS_ALLOWED = %w( PUT DELETE OPTIONS HEAD )
|
37
|
+
|
38
|
+
# Return the HTTP request method with support for method tunneling using the POST
|
39
|
+
# _method parameter hack. If the real request method is POST and a _method param is
|
40
|
+
# given and the value is one defined in +POST_TUNNEL_METHODS_ALLOWED+, return the value
|
41
|
+
# of the _method param instead.
|
42
|
+
def request_method
|
43
|
+
if post_tunnel_method_hack?
|
44
|
+
params['_method'].upcase
|
45
|
+
else
|
46
|
+
@env['REQUEST_METHOD']
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def user_agent
|
51
|
+
env['HTTP_USER_AGENT']
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
# Return truthfully if and only if the following conditions are met: 1.) the
|
57
|
+
# *actual* request method is POST, 2.) the request content-type is one of
|
58
|
+
# 'application/x-www-form-urlencoded' or 'multipart/form-data', 3.) there is a
|
59
|
+
# "_method" parameter in the POST body (not in the query string), and 4.) the
|
60
|
+
# method parameter is one of the verbs listed in the POST_TUNNEL_METHODS_ALLOWED
|
61
|
+
# list.
|
62
|
+
def post_tunnel_method_hack?
|
63
|
+
@env['REQUEST_METHOD'] == 'POST' &&
|
64
|
+
POST_TUNNEL_METHODS_ALLOWED.include?(self.POST.fetch('_method', '').upcase)
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
module Utils
|
70
|
+
extend self
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
module Sinatra
|
76
|
+
extend self
|
77
|
+
|
78
|
+
module Version
|
79
|
+
MAJOR = '0'
|
80
|
+
MINOR = '2'
|
81
|
+
REVISION = '0'
|
82
|
+
def self.combined
|
83
|
+
[MAJOR, MINOR, REVISION].join('.')
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
class NotFound < RuntimeError; end
|
88
|
+
class ServerError < RuntimeError; end
|
89
|
+
|
90
|
+
Result = Struct.new(:block, :params, :status) unless defined?(Result)
|
91
|
+
|
92
|
+
def options
|
93
|
+
application.options
|
94
|
+
end
|
95
|
+
|
96
|
+
def application
|
97
|
+
unless @app
|
98
|
+
@app = Application.new
|
99
|
+
Sinatra::Environment.setup!
|
100
|
+
end
|
101
|
+
@app
|
102
|
+
end
|
103
|
+
|
104
|
+
def application=(app)
|
105
|
+
@app = app
|
106
|
+
end
|
107
|
+
|
108
|
+
def port
|
109
|
+
application.options.port
|
110
|
+
end
|
111
|
+
|
112
|
+
def env
|
113
|
+
application.options.env
|
114
|
+
end
|
115
|
+
|
116
|
+
def build_application
|
117
|
+
app = application
|
118
|
+
app = Rack::Session::Cookie.new(app) if Sinatra.options.sessions == true
|
119
|
+
app = Rack::CommonLogger.new(app) if Sinatra.options.logging == true
|
120
|
+
app
|
121
|
+
end
|
122
|
+
|
123
|
+
def run
|
124
|
+
|
125
|
+
begin
|
126
|
+
puts "== Sinatra has taken the stage on port #{port} for #{env}"
|
127
|
+
require 'pp'
|
128
|
+
Rack::Handler::Mongrel.run(build_application, :Port => port) do |server|
|
129
|
+
trap(:INT) do
|
130
|
+
server.stop
|
131
|
+
puts "\n== Sinatra has ended his set (crowd applauds)"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
rescue Errno::EADDRINUSE => e
|
135
|
+
puts "== Someone is already performing on port #{port}!"
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
|
140
|
+
class Event
|
141
|
+
|
142
|
+
URI_CHAR = '[^/?:,&#\.]'.freeze unless defined?(URI_CHAR)
|
143
|
+
PARAM = /:(#{URI_CHAR}+)/.freeze unless defined?(PARAM)
|
144
|
+
SPLAT = /(.*?)/
|
145
|
+
attr_reader :path, :block, :param_keys, :pattern, :options
|
146
|
+
|
147
|
+
def initialize(path, options = {}, &b)
|
148
|
+
@path = URI.encode(path)
|
149
|
+
@block = b
|
150
|
+
@param_keys = []
|
151
|
+
@options = options
|
152
|
+
regex = @path.to_s.gsub(PARAM) do
|
153
|
+
@param_keys << $1
|
154
|
+
"(#{URI_CHAR}+)"
|
155
|
+
end
|
156
|
+
|
157
|
+
regex.gsub!('*', SPLAT.to_s)
|
158
|
+
|
159
|
+
@pattern = /^#{regex}$/
|
160
|
+
end
|
161
|
+
|
162
|
+
def invoke(request)
|
163
|
+
params = {}
|
164
|
+
if agent = options[:agent]
|
165
|
+
return unless request.user_agent =~ agent
|
166
|
+
params[:agent] = $~[1..-1]
|
167
|
+
end
|
168
|
+
if host = options[:host]
|
169
|
+
return unless host === request.host
|
170
|
+
end
|
171
|
+
return unless pattern =~ request.path_info.squeeze('/')
|
172
|
+
params.merge!(param_keys.zip($~.captures.map(&:from_param)).to_hash)
|
173
|
+
Result.new(block, params, 200)
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
177
|
+
|
178
|
+
class Error
|
179
|
+
|
180
|
+
attr_reader :code, :block
|
181
|
+
|
182
|
+
def initialize(code, &b)
|
183
|
+
@code, @block = code, b
|
184
|
+
end
|
185
|
+
|
186
|
+
def invoke(request)
|
187
|
+
Result.new(block, {}, 404)
|
188
|
+
end
|
189
|
+
|
190
|
+
end
|
191
|
+
|
192
|
+
class Static
|
193
|
+
|
194
|
+
def invoke(request)
|
195
|
+
return unless File.file?(
|
196
|
+
Sinatra.application.options.public + request.path_info
|
197
|
+
)
|
198
|
+
Result.new(block, {}, 200)
|
199
|
+
end
|
200
|
+
|
201
|
+
def block
|
202
|
+
Proc.new do
|
203
|
+
send_file Sinatra.application.options.public + request.path_info,
|
204
|
+
:disposition => nil
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|
209
|
+
|
210
|
+
# Adapted from actionpack
|
211
|
+
# Methods for sending files and streams to the browser instead of rendering.
|
212
|
+
module Streaming
|
213
|
+
DEFAULT_SEND_FILE_OPTIONS = {
|
214
|
+
:type => 'application/octet-stream'.freeze,
|
215
|
+
:disposition => 'attachment'.freeze,
|
216
|
+
:stream => true,
|
217
|
+
:buffer_size => 4096
|
218
|
+
}.freeze
|
219
|
+
|
220
|
+
class MissingFile < RuntimeError; end
|
221
|
+
|
222
|
+
class FileStreamer
|
223
|
+
|
224
|
+
attr_reader :path, :options
|
225
|
+
|
226
|
+
def initialize(path, options)
|
227
|
+
@path, @options = path, options
|
228
|
+
end
|
229
|
+
|
230
|
+
def to_result(cx, *args)
|
231
|
+
self
|
232
|
+
end
|
233
|
+
|
234
|
+
def each
|
235
|
+
File.open(path, 'rb') do |file|
|
236
|
+
while buf = file.read(options[:buffer_size])
|
237
|
+
yield buf
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
end
|
243
|
+
|
244
|
+
protected
|
245
|
+
# Sends the file by streaming it 4096 bytes at a time. This way the
|
246
|
+
# whole file doesn't need to be read into memory at once. This makes
|
247
|
+
# it feasible to send even large files.
|
248
|
+
#
|
249
|
+
# Be careful to sanitize the path parameter if it coming from a web
|
250
|
+
# page. send_file(params[:path]) allows a malicious user to
|
251
|
+
# download any file on your server.
|
252
|
+
#
|
253
|
+
# Options:
|
254
|
+
# * <tt>:filename</tt> - suggests a filename for the browser to use.
|
255
|
+
# Defaults to File.basename(path).
|
256
|
+
# * <tt>:type</tt> - specifies an HTTP content type.
|
257
|
+
# Defaults to 'application/octet-stream'.
|
258
|
+
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
|
259
|
+
# Valid values are 'inline' and 'attachment' (default). When set to nil, the
|
260
|
+
# Content-Disposition and Content-Transfer-Encoding headers are omitted entirely.
|
261
|
+
# * <tt>:stream</tt> - whether to send the file to the user agent as it is read (true)
|
262
|
+
# or to read the entire file before sending (false). Defaults to true.
|
263
|
+
# * <tt>:buffer_size</tt> - specifies size (in bytes) of the buffer used to stream the file.
|
264
|
+
# Defaults to 4096.
|
265
|
+
# * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'.
|
266
|
+
# * <tt>:last_modified</tt> - an optional RFC 2616 formatted date value (See Time#httpdate)
|
267
|
+
# indicating the last modified time of the file. If the request includes an
|
268
|
+
# If-Modified-Since header that matches this value exactly, a 304 Not Modified response
|
269
|
+
# is sent instead of the file. Defaults to the file's last modified
|
270
|
+
# time.
|
271
|
+
#
|
272
|
+
# The default Content-Type and Content-Disposition headers are
|
273
|
+
# set to download arbitrary binary files in as many browsers as
|
274
|
+
# possible. IE versions 4, 5, 5.5, and 6 are all known to have
|
275
|
+
# a variety of quirks (especially when downloading over SSL).
|
276
|
+
#
|
277
|
+
# Simple download:
|
278
|
+
# send_file '/path/to.zip'
|
279
|
+
#
|
280
|
+
# Show a JPEG in the browser:
|
281
|
+
# send_file '/path/to.jpeg', :type => 'image/jpeg', :disposition => 'inline'
|
282
|
+
#
|
283
|
+
# Show a 404 page in the browser:
|
284
|
+
# send_file '/path/to/404.html, :type => 'text/html; charset=utf-8', :status => 404
|
285
|
+
#
|
286
|
+
# Read about the other Content-* HTTP headers if you'd like to
|
287
|
+
# provide the user with more information (such as Content-Description).
|
288
|
+
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11
|
289
|
+
#
|
290
|
+
# Also be aware that the document may be cached by proxies and browsers.
|
291
|
+
# The Pragma and Cache-Control headers declare how the file may be cached
|
292
|
+
# by intermediaries. They default to require clients to validate with
|
293
|
+
# the server before releasing cached responses. See
|
294
|
+
# http://www.mnot.net/cache_docs/ for an overview of web caching and
|
295
|
+
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
|
296
|
+
# for the Cache-Control header spec.
|
297
|
+
def send_file(path, options = {}) #:doc:
|
298
|
+
raise MissingFile, "Cannot read file #{path}" unless File.file?(path) and File.readable?(path)
|
299
|
+
|
300
|
+
options[:length] ||= File.size(path)
|
301
|
+
options[:filename] ||= File.basename(path)
|
302
|
+
options[:type] ||= Rack::File::MIME_TYPES[File.extname(options[:filename])[1..-1]] || 'text/plain'
|
303
|
+
options[:last_modified] ||= File.mtime(path).httpdate
|
304
|
+
send_file_headers! options
|
305
|
+
|
306
|
+
if options[:stream]
|
307
|
+
throw :halt, [options[:status] || 200, FileStreamer.new(path, options)]
|
308
|
+
else
|
309
|
+
File.open(path, 'rb') { |file| throw :halt, [options[:status] || 200, file.read] }
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
# Send binary data to the user as a file download. May set content type, apparent file name,
|
314
|
+
# and specify whether to show data inline or download as an attachment.
|
315
|
+
#
|
316
|
+
# Options:
|
317
|
+
# * <tt>:filename</tt> - Suggests a filename for the browser to use.
|
318
|
+
# * <tt>:type</tt> - specifies an HTTP content type.
|
319
|
+
# Defaults to 'application/octet-stream'.
|
320
|
+
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
|
321
|
+
# Valid values are 'inline' and 'attachment' (default).
|
322
|
+
# * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'.
|
323
|
+
# * <tt>:last_modified</tt> - an optional RFC 2616 formatted date value (See Time#httpdate)
|
324
|
+
# indicating the last modified time of the response entity. If the request includes an
|
325
|
+
# If-Modified-Since header that matches this value exactly, a 304 Not Modified response
|
326
|
+
# is sent instead of the data.
|
327
|
+
#
|
328
|
+
# Generic data download:
|
329
|
+
# send_data buffer
|
330
|
+
#
|
331
|
+
# Download a dynamically-generated tarball:
|
332
|
+
# send_data generate_tgz('dir'), :filename => 'dir.tgz'
|
333
|
+
#
|
334
|
+
# Display an image Active Record in the browser:
|
335
|
+
# send_data image.data, :type => image.content_type, :disposition => 'inline'
|
336
|
+
#
|
337
|
+
# See +send_file+ for more information on HTTP Content-* headers and caching.
|
338
|
+
def send_data(data, options = {}) #:doc:
|
339
|
+
send_file_headers! options.merge(:length => data.size)
|
340
|
+
throw :halt, [options[:status] || 200, data]
|
341
|
+
end
|
342
|
+
|
343
|
+
private
|
344
|
+
def send_file_headers!(options)
|
345
|
+
options = DEFAULT_SEND_FILE_OPTIONS.merge(options)
|
346
|
+
[:length, :type, :disposition].each do |arg|
|
347
|
+
raise ArgumentError, ":#{arg} option required" unless options.key?(arg)
|
348
|
+
end
|
349
|
+
|
350
|
+
# Send a "304 Not Modified" if the last_modified option is provided and matches
|
351
|
+
# the If-Modified-Since request header value.
|
352
|
+
if last_modified = options[:last_modified]
|
353
|
+
header 'Last-Modified' => last_modified
|
354
|
+
throw :halt, [ 304, '' ] if last_modified == request.env['HTTP_IF_MODIFIED_SINCE']
|
355
|
+
end
|
356
|
+
|
357
|
+
headers(
|
358
|
+
'Content-Length' => options[:length].to_s,
|
359
|
+
'Content-Type' => options[:type].strip # fixes a problem with extra '\r' with some browsers
|
360
|
+
)
|
361
|
+
|
362
|
+
# Omit Content-Disposition and Content-Transfer-Encoding headers if
|
363
|
+
# the :disposition option set to nil.
|
364
|
+
if !options[:disposition].nil?
|
365
|
+
disposition = options[:disposition].dup || 'attachment'
|
366
|
+
disposition <<= %(; filename="#{options[:filename]}") if options[:filename]
|
367
|
+
headers 'Content-Disposition' => disposition, 'Content-Transfer-Encoding' => 'binary'
|
368
|
+
end
|
369
|
+
|
370
|
+
# Fix a problem with IE 6.0 on opening downloaded files:
|
371
|
+
# If Cache-Control: no-cache is set (which Rails does by default),
|
372
|
+
# IE removes the file it just downloaded from its cache immediately
|
373
|
+
# after it displays the "open/save" dialog, which means that if you
|
374
|
+
# hit "open" the file isn't there anymore when the application that
|
375
|
+
# is called for handling the download is run, so let's workaround that
|
376
|
+
header('Cache-Control' => 'private') if headers['Cache-Control'] == 'no-cache'
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
module ResponseHelpers
|
381
|
+
|
382
|
+
def redirect(path, *args)
|
383
|
+
status(302)
|
384
|
+
headers 'Location' => path
|
385
|
+
throw :halt, *args
|
386
|
+
end
|
387
|
+
|
388
|
+
def headers(header = nil)
|
389
|
+
@response.headers.merge!(header) if header
|
390
|
+
@response.headers
|
391
|
+
end
|
392
|
+
alias :header :headers
|
393
|
+
|
394
|
+
end
|
395
|
+
|
396
|
+
module RenderingHelpers
|
397
|
+
|
398
|
+
def render(renderer, template, options={})
|
399
|
+
m = method("render_#{renderer}")
|
400
|
+
result = m.call(resolve_template(renderer, template, options), options)
|
401
|
+
if layout = determine_layout(renderer, template, options)
|
402
|
+
result = m.call(resolve_template(renderer, layout, options), options) { result }
|
403
|
+
end
|
404
|
+
result
|
405
|
+
end
|
406
|
+
|
407
|
+
def determine_layout(renderer, template, options)
|
408
|
+
return if options[:layout] == false
|
409
|
+
layout_from_options = options[:layout] || :layout
|
410
|
+
resolve_template(renderer, layout_from_options, options, false)
|
411
|
+
end
|
412
|
+
|
413
|
+
private
|
414
|
+
|
415
|
+
def resolve_template(renderer, template, options, scream = true)
|
416
|
+
case template
|
417
|
+
when String
|
418
|
+
template
|
419
|
+
when Proc
|
420
|
+
template.call
|
421
|
+
when Symbol
|
422
|
+
if proc = templates[template]
|
423
|
+
resolve_template(renderer, proc, options, scream)
|
424
|
+
else
|
425
|
+
read_template_file(renderer, template, options, scream)
|
426
|
+
end
|
427
|
+
else
|
428
|
+
nil
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
def read_template_file(renderer, template, options, scream = true)
|
433
|
+
path = File.join(
|
434
|
+
options[:views_directory] || Sinatra.application.options.views,
|
435
|
+
"#{template}.#{renderer}"
|
436
|
+
)
|
437
|
+
unless File.exists?(path)
|
438
|
+
raise Errno::ENOENT.new(path) if scream
|
439
|
+
nil
|
440
|
+
else
|
441
|
+
File.read(path)
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
def templates
|
446
|
+
Sinatra.application.templates
|
447
|
+
end
|
448
|
+
|
449
|
+
end
|
450
|
+
|
451
|
+
module Erb
|
452
|
+
|
453
|
+
def erb(content, options={})
|
454
|
+
require 'erb'
|
455
|
+
render(:erb, content, options)
|
456
|
+
end
|
457
|
+
|
458
|
+
private
|
459
|
+
|
460
|
+
def render_erb(content, options = {})
|
461
|
+
::ERB.new(content).result(binding)
|
462
|
+
end
|
463
|
+
|
464
|
+
end
|
465
|
+
|
466
|
+
module Haml
|
467
|
+
|
468
|
+
def haml(content, options={})
|
469
|
+
require 'haml'
|
470
|
+
render(:haml, content, options)
|
471
|
+
end
|
472
|
+
|
473
|
+
private
|
474
|
+
|
475
|
+
def render_haml(content, options = {}, &b)
|
476
|
+
::Haml::Engine.new(content).render(options[:scope] || self, options[:locals] || {}, &b)
|
477
|
+
end
|
478
|
+
|
479
|
+
end
|
480
|
+
|
481
|
+
# Generate valid CSS using Sass (part of Haml)
|
482
|
+
#
|
483
|
+
# Sass templates can be in external files with <tt>.sass</tt> extension or can use Sinatra's
|
484
|
+
# in_file_templates. In either case, the file can be rendered by passing the name of
|
485
|
+
# the template to the +sass+ method as a symbol.
|
486
|
+
#
|
487
|
+
# Unlike Haml, Sass does not support a layout file, so the +sass+ method will ignore both
|
488
|
+
# the default <tt>layout.sass</tt> file and any parameters passed in as <tt>:layout</tt> in
|
489
|
+
# the options hash.
|
490
|
+
#
|
491
|
+
# === Sass Template Files
|
492
|
+
#
|
493
|
+
# Sass templates can be stored in separate files with a <tt>.sass</tt>
|
494
|
+
# extension under the view path.
|
495
|
+
#
|
496
|
+
# Example:
|
497
|
+
# get '/stylesheet.css' do
|
498
|
+
# header 'Content-Type' => 'text/css; charset=utf-8'
|
499
|
+
# sass :stylesheet
|
500
|
+
# end
|
501
|
+
#
|
502
|
+
# The "views/stylesheet.sass" file might contain the following:
|
503
|
+
#
|
504
|
+
# body
|
505
|
+
# #admin
|
506
|
+
# :background-color #CCC
|
507
|
+
# #main
|
508
|
+
# :background-color #000
|
509
|
+
# #form
|
510
|
+
# :border-color #AAA
|
511
|
+
# :border-width 10px
|
512
|
+
#
|
513
|
+
# And yields the following output:
|
514
|
+
#
|
515
|
+
# body #admin {
|
516
|
+
# background-color: #CCC; }
|
517
|
+
# body #main {
|
518
|
+
# background-color: #000; }
|
519
|
+
#
|
520
|
+
# #form {
|
521
|
+
# border-color: #AAA;
|
522
|
+
# border-width: 10px; }
|
523
|
+
#
|
524
|
+
#
|
525
|
+
# NOTE: Haml must be installed or a LoadError will be raised the first time an
|
526
|
+
# attempt is made to render a Sass template.
|
527
|
+
#
|
528
|
+
# See http://haml.hamptoncatlin.com/docs/rdoc/classes/Sass.html for comprehensive documentation on Sass.
|
529
|
+
|
530
|
+
|
531
|
+
module Sass
|
532
|
+
|
533
|
+
def sass(content, options = {})
|
534
|
+
require 'sass'
|
535
|
+
|
536
|
+
# Sass doesn't support a layout, so we override any possible layout here
|
537
|
+
options[:layout] = false
|
538
|
+
|
539
|
+
render(:sass, content, options)
|
540
|
+
end
|
541
|
+
|
542
|
+
private
|
543
|
+
|
544
|
+
def render_sass(content, options = {})
|
545
|
+
::Sass::Engine.new(content).render
|
546
|
+
end
|
547
|
+
end
|
548
|
+
|
549
|
+
# Generating conservative XML content using Builder templates.
|
550
|
+
#
|
551
|
+
# Builder templates can be inline by passing a block to the builder method, or in
|
552
|
+
# external files with +.builder+ extension by passing the name of the template
|
553
|
+
# to the +builder+ method as a Symbol.
|
554
|
+
#
|
555
|
+
# === Inline Rendering
|
556
|
+
#
|
557
|
+
# If the builder method is given a block, the block is called directly with an
|
558
|
+
# +XmlMarkup+ instance and the result is returned as String:
|
559
|
+
# get '/who.xml' do
|
560
|
+
# builder do |xml|
|
561
|
+
# xml.instruct!
|
562
|
+
# xml.person do
|
563
|
+
# xml.name "Francis Albert Sinatra",
|
564
|
+
# :aka => "Frank Sinatra"
|
565
|
+
# xml.email 'frank@capitolrecords.com'
|
566
|
+
# end
|
567
|
+
# end
|
568
|
+
# end
|
569
|
+
#
|
570
|
+
# Yields the following XML:
|
571
|
+
# <?xml version='1.0' encoding='UTF-8'?>
|
572
|
+
# <person>
|
573
|
+
# <name aka='Frank Sinatra'>Francis Albert Sinatra</name>
|
574
|
+
# <email>Frank Sinatra</email>
|
575
|
+
# </person>
|
576
|
+
#
|
577
|
+
# === Builder Template Files
|
578
|
+
#
|
579
|
+
# Builder templates can be stored in separate files with a +.builder+
|
580
|
+
# extension under the view path. An +XmlMarkup+ object named +xml+ is automatically
|
581
|
+
# made available to template.
|
582
|
+
#
|
583
|
+
# Example:
|
584
|
+
# get '/bio.xml' do
|
585
|
+
# builder :bio
|
586
|
+
# end
|
587
|
+
#
|
588
|
+
# The "views/bio.builder" file might contain the following:
|
589
|
+
# xml.instruct! :xml, :version => '1.1'
|
590
|
+
# xml.person do
|
591
|
+
# xml.name "Francis Albert Sinatra"
|
592
|
+
# xml.aka "Frank Sinatra"
|
593
|
+
# xml.aka "Ol' Blue Eyes"
|
594
|
+
# xml.aka "The Chairman of the Board"
|
595
|
+
# xml.born 'date' => '1915-12-12' do
|
596
|
+
# xml.text! "Hoboken, New Jersey, U.S.A."
|
597
|
+
# end
|
598
|
+
# xml.died 'age' => 82
|
599
|
+
# end
|
600
|
+
#
|
601
|
+
# And yields the following output:
|
602
|
+
# <?xml version='1.1' encoding='UTF-8'?>
|
603
|
+
# <person>
|
604
|
+
# <name>Francis Albert Sinatra</name>
|
605
|
+
# <aka>Frank Sinatra</aka>
|
606
|
+
# <aka>Ol' Blue Eyes</aka>
|
607
|
+
# <aka>The Chairman of the Board</aka>
|
608
|
+
# <born date='1915-12-12'>Hoboken, New Jersey, U.S.A.</born>
|
609
|
+
# <died age='82' />
|
610
|
+
# </person>
|
611
|
+
#
|
612
|
+
# NOTE: Builder must be installed or a LoadError will be raised the first time an
|
613
|
+
# attempt is made to render a builder template.
|
614
|
+
#
|
615
|
+
# See http://builder.rubyforge.org/ for comprehensive documentation on Builder.
|
616
|
+
module Builder
|
617
|
+
|
618
|
+
def builder(content=nil, options={}, &block)
|
619
|
+
options, content = content, nil if content.is_a?(Hash)
|
620
|
+
content = Proc.new { block } if content.nil?
|
621
|
+
render(:builder, content, options)
|
622
|
+
end
|
623
|
+
|
624
|
+
private
|
625
|
+
|
626
|
+
def render_builder(content, options = {}, &b)
|
627
|
+
require 'builder'
|
628
|
+
xml = ::Builder::XmlMarkup.new(:indent => 2)
|
629
|
+
case content
|
630
|
+
when String
|
631
|
+
eval(content, binding, '<BUILDER>', 1)
|
632
|
+
when Proc
|
633
|
+
content.call(xml)
|
634
|
+
end
|
635
|
+
xml.target!
|
636
|
+
end
|
637
|
+
|
638
|
+
end
|
639
|
+
|
640
|
+
class EventContext
|
641
|
+
|
642
|
+
include ResponseHelpers
|
643
|
+
include Streaming
|
644
|
+
include RenderingHelpers
|
645
|
+
include Erb
|
646
|
+
include Haml
|
647
|
+
include Builder
|
648
|
+
include Sass
|
649
|
+
|
650
|
+
attr_accessor :request, :response
|
651
|
+
|
652
|
+
dslify_writer :status, :body
|
653
|
+
|
654
|
+
def initialize(request, response, route_params)
|
655
|
+
@request = request
|
656
|
+
@response = response
|
657
|
+
@route_params = route_params
|
658
|
+
@response.body = nil
|
659
|
+
end
|
660
|
+
|
661
|
+
def params
|
662
|
+
@params ||= begin
|
663
|
+
h = Hash.new {|h,k| h[k.to_s] if Symbol === k}
|
664
|
+
h.merge(@route_params.merge(@request.params))
|
665
|
+
end
|
666
|
+
end
|
667
|
+
|
668
|
+
def data
|
669
|
+
@data ||= params.keys.first
|
670
|
+
end
|
671
|
+
|
672
|
+
def stop(*args)
|
673
|
+
throw :halt, args
|
674
|
+
end
|
675
|
+
|
676
|
+
def complete(returned)
|
677
|
+
@response.body || returned
|
678
|
+
end
|
679
|
+
|
680
|
+
def session
|
681
|
+
@request.env['rack.session'] || {}
|
682
|
+
end
|
683
|
+
|
684
|
+
private
|
685
|
+
|
686
|
+
def method_missing(name, *args, &b)
|
687
|
+
@response.send(name, *args, &b)
|
688
|
+
end
|
689
|
+
|
690
|
+
end
|
691
|
+
|
692
|
+
class Application
|
693
|
+
|
694
|
+
attr_reader :events, :errors, :templates, :filters
|
695
|
+
attr_reader :clearables, :reloading
|
696
|
+
|
697
|
+
attr_writer :options
|
698
|
+
|
699
|
+
def self.default_options
|
700
|
+
@@default_options ||= {
|
701
|
+
:run => true,
|
702
|
+
:port => 4567,
|
703
|
+
:env => :development,
|
704
|
+
:root => Dir.pwd,
|
705
|
+
:views => Dir.pwd + '/views',
|
706
|
+
:public => Dir.pwd + '/public',
|
707
|
+
:sessions => false,
|
708
|
+
:logging => true,
|
709
|
+
}
|
710
|
+
end
|
711
|
+
|
712
|
+
def default_options
|
713
|
+
self.class.default_options
|
714
|
+
end
|
715
|
+
|
716
|
+
|
717
|
+
##
|
718
|
+
# Load all options given on the command line
|
719
|
+
# NOTE: Ignores --name so unit/spec tests can run individually
|
720
|
+
def load_options!
|
721
|
+
require 'optparse'
|
722
|
+
OptionParser.new do |op|
|
723
|
+
op.on('-p port') { |port| default_options[:port] = port }
|
724
|
+
op.on('-e env') { |env| default_options[:env] = env }
|
725
|
+
op.on('-x') { |env| default_options[:mutex] = true }
|
726
|
+
end.parse!(ARGV.dup.select { |o| o !~ /--name/ })
|
727
|
+
end
|
728
|
+
|
729
|
+
# Called immediately after the application is initialized or reloaded to
|
730
|
+
# register default events. Events added here have dibs on requests since
|
731
|
+
# they appear first in the list.
|
732
|
+
def load_default_events!
|
733
|
+
events[:get] << Static.new
|
734
|
+
end
|
735
|
+
|
736
|
+
def initialize
|
737
|
+
@clearables = [
|
738
|
+
@events = Hash.new { |hash, key| hash[key] = [] },
|
739
|
+
@errors = Hash.new,
|
740
|
+
@filters = Hash.new { |hash, key| hash[key] = [] },
|
741
|
+
@templates = Hash.new
|
742
|
+
]
|
743
|
+
load_options!
|
744
|
+
load_default_events!
|
745
|
+
end
|
746
|
+
|
747
|
+
def define_event(method, path, options = {}, &b)
|
748
|
+
events[method] << event = Event.new(path, options, &b)
|
749
|
+
event
|
750
|
+
end
|
751
|
+
|
752
|
+
def define_template(name=:layout, &b)
|
753
|
+
templates[name] = b
|
754
|
+
end
|
755
|
+
|
756
|
+
def define_error(code, options = {}, &b)
|
757
|
+
errors[code] = Error.new(code, &b)
|
758
|
+
end
|
759
|
+
|
760
|
+
def define_filter(type, &b)
|
761
|
+
filters[:before] << b
|
762
|
+
end
|
763
|
+
|
764
|
+
# Visits and invokes each handler registered for the +request_method+ in
|
765
|
+
# definition order until a Result response is produced. If no handler
|
766
|
+
# responds with a Result, the NotFound error handler is invoked.
|
767
|
+
#
|
768
|
+
# When the request_method is "HEAD" and no valid Result is produced by
|
769
|
+
# the set of handlers registered for HEAD requests, an attempt is made to
|
770
|
+
# invoke the GET handlers to generate the response before resorting to the
|
771
|
+
# default error handler.
|
772
|
+
def lookup(request)
|
773
|
+
method = request.request_method.downcase.to_sym
|
774
|
+
events[method].eject(&[:invoke, request]) ||
|
775
|
+
(events[:get].eject(&[:invoke, request]) if method == :head) ||
|
776
|
+
errors[NotFound].invoke(request)
|
777
|
+
end
|
778
|
+
|
779
|
+
def options
|
780
|
+
@options ||= OpenStruct.new(default_options)
|
781
|
+
end
|
782
|
+
|
783
|
+
def development?
|
784
|
+
options.env == :development
|
785
|
+
end
|
786
|
+
|
787
|
+
def reload!
|
788
|
+
@reloading = true
|
789
|
+
clearables.each(&:clear)
|
790
|
+
load_default_events!
|
791
|
+
Kernel.load $0
|
792
|
+
@reloading = false
|
793
|
+
Environment.setup!
|
794
|
+
end
|
795
|
+
|
796
|
+
def mutex
|
797
|
+
@@mutex ||= Mutex.new
|
798
|
+
end
|
799
|
+
|
800
|
+
def run_safely
|
801
|
+
if options.mutex
|
802
|
+
mutex.synchronize { yield }
|
803
|
+
else
|
804
|
+
yield
|
805
|
+
end
|
806
|
+
end
|
807
|
+
|
808
|
+
def call(env)
|
809
|
+
reload! if development?
|
810
|
+
request = Rack::Request.new(env)
|
811
|
+
result = lookup(request)
|
812
|
+
context = EventContext.new(
|
813
|
+
request,
|
814
|
+
Rack::Response.new,
|
815
|
+
result.params
|
816
|
+
)
|
817
|
+
context.status(result.status)
|
818
|
+
begin
|
819
|
+
returned = run_safely do
|
820
|
+
catch(:halt) do
|
821
|
+
filters[:before].each { |f| context.instance_eval(&f) }
|
822
|
+
[:complete, context.instance_eval(&result.block)]
|
823
|
+
end
|
824
|
+
end
|
825
|
+
body = returned.to_result(context)
|
826
|
+
rescue => e
|
827
|
+
request.env['sinatra.error'] = e
|
828
|
+
context.status(500)
|
829
|
+
result = (errors[e.class] || errors[ServerError]).invoke(request)
|
830
|
+
returned = run_safely do
|
831
|
+
catch(:halt) do
|
832
|
+
[:complete, context.instance_eval(&result.block)]
|
833
|
+
end
|
834
|
+
end
|
835
|
+
body = returned.to_result(context)
|
836
|
+
end
|
837
|
+
body = '' unless body.respond_to?(:each)
|
838
|
+
body = '' if request.request_method.upcase == 'HEAD'
|
839
|
+
context.body = body.kind_of?(String) ? [*body] : body
|
840
|
+
context.finish
|
841
|
+
end
|
842
|
+
|
843
|
+
end
|
844
|
+
|
845
|
+
|
846
|
+
module Environment
|
847
|
+
extend self
|
848
|
+
|
849
|
+
def setup!
|
850
|
+
configure do
|
851
|
+
error do
|
852
|
+
raise request.env['sinatra.error'] if Sinatra.options.raise_errors
|
853
|
+
'<h1>Internal Server Error</h1>'
|
854
|
+
end
|
855
|
+
not_found { '<h1>Not Found</h1>'}
|
856
|
+
end
|
857
|
+
|
858
|
+
configures :development do
|
859
|
+
|
860
|
+
get '/sinatra_custom_images/:image.png' do
|
861
|
+
File.read(File.dirname(__FILE__) + "/../images/#{params[:image]}.png")
|
862
|
+
end
|
863
|
+
|
864
|
+
not_found do
|
865
|
+
%Q(
|
866
|
+
<style>
|
867
|
+
body {
|
868
|
+
text-align: center;
|
869
|
+
color: #888;
|
870
|
+
font-family: Arial;
|
871
|
+
font-size: 22px;
|
872
|
+
margin: 20px;
|
873
|
+
}
|
874
|
+
#content {
|
875
|
+
margin: 0 auto;
|
876
|
+
width: 500px;
|
877
|
+
text-align: left;
|
878
|
+
}
|
879
|
+
</style>
|
880
|
+
<html>
|
881
|
+
<body>
|
882
|
+
<h2>Sinatra doesn't know this diddy.</h2>
|
883
|
+
<img src='/sinatra_custom_images/404.png'></img>
|
884
|
+
<div id="content">
|
885
|
+
Try this:
|
886
|
+
<pre>#{request.request_method.downcase} "#{request.path_info}" do
|
887
|
+
.. do something ..
|
888
|
+
end<pre>
|
889
|
+
</div>
|
890
|
+
</body>
|
891
|
+
</html>
|
892
|
+
)
|
893
|
+
end
|
894
|
+
|
895
|
+
error do
|
896
|
+
@error = request.env['sinatra.error']
|
897
|
+
%Q(
|
898
|
+
<html>
|
899
|
+
<body>
|
900
|
+
<style type="text/css" media="screen">
|
901
|
+
body {
|
902
|
+
font-family: Verdana;
|
903
|
+
color: #333;
|
904
|
+
}
|
905
|
+
|
906
|
+
#content {
|
907
|
+
width: 700px;
|
908
|
+
margin-left: 20px;
|
909
|
+
}
|
910
|
+
|
911
|
+
#content h1 {
|
912
|
+
width: 99%;
|
913
|
+
color: #1D6B8D;
|
914
|
+
font-weight: bold;
|
915
|
+
}
|
916
|
+
|
917
|
+
#stacktrace {
|
918
|
+
margin-top: -20px;
|
919
|
+
}
|
920
|
+
|
921
|
+
#stacktrace pre {
|
922
|
+
font-size: 12px;
|
923
|
+
border-left: 2px solid #ddd;
|
924
|
+
padding-left: 10px;
|
925
|
+
}
|
926
|
+
|
927
|
+
#stacktrace img {
|
928
|
+
margin-top: 10px;
|
929
|
+
}
|
930
|
+
</style>
|
931
|
+
<div id="content">
|
932
|
+
<img src="/sinatra_custom_images/500.png" />
|
933
|
+
<div class="info">
|
934
|
+
Params: <pre>#{params.inspect}
|
935
|
+
</div>
|
936
|
+
<div id="stacktrace">
|
937
|
+
<h1>#{Rack::Utils.escape_html(@error.class.name + ' - ' + @error.message)}</h1>
|
938
|
+
<pre><code>#{Rack::Utils.escape_html(@error.backtrace.join("\n"))}</code></pre>
|
939
|
+
</div>
|
940
|
+
</body>
|
941
|
+
</html>
|
942
|
+
)
|
943
|
+
end
|
944
|
+
end
|
945
|
+
end
|
946
|
+
end
|
947
|
+
|
948
|
+
end
|
949
|
+
|
950
|
+
def get(path, options ={}, &b)
|
951
|
+
Sinatra.application.define_event(:get, path, options, &b)
|
952
|
+
end
|
953
|
+
|
954
|
+
def post(path, options ={}, &b)
|
955
|
+
Sinatra.application.define_event(:post, path, options, &b)
|
956
|
+
end
|
957
|
+
|
958
|
+
def put(path, options ={}, &b)
|
959
|
+
Sinatra.application.define_event(:put, path, options, &b)
|
960
|
+
end
|
961
|
+
|
962
|
+
def delete(path, options ={}, &b)
|
963
|
+
Sinatra.application.define_event(:delete, path, options, &b)
|
964
|
+
end
|
965
|
+
|
966
|
+
def before(&b)
|
967
|
+
Sinatra.application.define_filter(:before, &b)
|
968
|
+
end
|
969
|
+
|
970
|
+
def helpers(&b)
|
971
|
+
Sinatra::EventContext.class_eval(&b)
|
972
|
+
end
|
973
|
+
|
974
|
+
def error(type = Sinatra::ServerError, options = {}, &b)
|
975
|
+
Sinatra.application.define_error(type, options, &b)
|
976
|
+
end
|
977
|
+
|
978
|
+
def not_found(options = {}, &b)
|
979
|
+
Sinatra.application.define_error(Sinatra::NotFound, options, &b)
|
980
|
+
end
|
981
|
+
|
982
|
+
def layout(name = :layout, &b)
|
983
|
+
Sinatra.application.define_template(name, &b)
|
984
|
+
end
|
985
|
+
|
986
|
+
def template(name, &b)
|
987
|
+
Sinatra.application.define_template(name, &b)
|
988
|
+
end
|
989
|
+
|
990
|
+
def use_in_file_templates!
|
991
|
+
require 'stringio'
|
992
|
+
templates = IO.read(caller.first.split(':').first).split('__FILE__').last
|
993
|
+
data = StringIO.new(templates)
|
994
|
+
current_template = nil
|
995
|
+
data.each do |line|
|
996
|
+
if line =~ /^##\s?(.*)/
|
997
|
+
current_template = $1.to_sym
|
998
|
+
Sinatra.application.templates[current_template] = ''
|
999
|
+
elsif current_template
|
1000
|
+
Sinatra.application.templates[current_template] << line
|
1001
|
+
end
|
1002
|
+
end
|
1003
|
+
end
|
1004
|
+
|
1005
|
+
def configures(*envs, &b)
|
1006
|
+
yield if !Sinatra.application.reloading &&
|
1007
|
+
(envs.include?(Sinatra.application.options.env) ||
|
1008
|
+
envs.empty?)
|
1009
|
+
end
|
1010
|
+
alias :configure :configures
|
1011
|
+
|
1012
|
+
def set_options(opts)
|
1013
|
+
Sinatra::Application.default_options.merge!(opts)
|
1014
|
+
Sinatra.application.options = nil
|
1015
|
+
end
|
1016
|
+
|
1017
|
+
def set_option(key, value)
|
1018
|
+
set_options(key => value)
|
1019
|
+
end
|
1020
|
+
|
1021
|
+
def mime(ext, type)
|
1022
|
+
Rack::File::MIME_TYPES[ext.to_s] = type
|
1023
|
+
end
|
1024
|
+
|
1025
|
+
### Misc Core Extensions
|
1026
|
+
|
1027
|
+
module Kernel
|
1028
|
+
|
1029
|
+
def silence_warnings
|
1030
|
+
old_verbose, $VERBOSE = $VERBOSE, nil
|
1031
|
+
yield
|
1032
|
+
ensure
|
1033
|
+
$VERBOSE = old_verbose
|
1034
|
+
end
|
1035
|
+
|
1036
|
+
end
|
1037
|
+
|
1038
|
+
class String
|
1039
|
+
|
1040
|
+
# Converts +self+ to an escaped URI parameter value
|
1041
|
+
# 'Foo Bar'.to_param # => 'Foo%20Bar'
|
1042
|
+
def to_param
|
1043
|
+
URI.escape(self)
|
1044
|
+
end
|
1045
|
+
|
1046
|
+
# Converts +self+ from an escaped URI parameter value
|
1047
|
+
# 'Foo%20Bar'.from_param # => 'Foo Bar'
|
1048
|
+
def from_param
|
1049
|
+
URI.unescape(self)
|
1050
|
+
end
|
1051
|
+
|
1052
|
+
end
|
1053
|
+
|
1054
|
+
class Hash
|
1055
|
+
|
1056
|
+
def to_params
|
1057
|
+
map { |k,v| "#{k}=#{URI.escape(v)}" }.join('&')
|
1058
|
+
end
|
1059
|
+
|
1060
|
+
def symbolize_keys
|
1061
|
+
self.inject({}) { |h,(k,v)| h[k.to_sym] = v; h }
|
1062
|
+
end
|
1063
|
+
|
1064
|
+
def pass(*keys)
|
1065
|
+
reject { |k,v| !keys.include?(k) }
|
1066
|
+
end
|
1067
|
+
|
1068
|
+
end
|
1069
|
+
|
1070
|
+
class Symbol
|
1071
|
+
|
1072
|
+
def to_proc
|
1073
|
+
Proc.new { |*args| args.shift.__send__(self, *args) }
|
1074
|
+
end
|
1075
|
+
|
1076
|
+
end
|
1077
|
+
|
1078
|
+
class Array
|
1079
|
+
|
1080
|
+
def to_hash
|
1081
|
+
self.inject({}) { |h, (k, v)| h[k] = v; h }
|
1082
|
+
end
|
1083
|
+
|
1084
|
+
def to_proc
|
1085
|
+
Proc.new { |*args| args.shift.__send__(self[0], *(args + self[1..-1])) }
|
1086
|
+
end
|
1087
|
+
|
1088
|
+
end
|
1089
|
+
|
1090
|
+
module Enumerable
|
1091
|
+
|
1092
|
+
def eject(&block)
|
1093
|
+
find { |e| result = block[e] and break result }
|
1094
|
+
end
|
1095
|
+
|
1096
|
+
end
|
1097
|
+
|
1098
|
+
### Core Extension results for throw :halt
|
1099
|
+
|
1100
|
+
class Proc
|
1101
|
+
def to_result(cx, *args)
|
1102
|
+
cx.instance_eval(&self)
|
1103
|
+
args.shift.to_result(cx, *args)
|
1104
|
+
end
|
1105
|
+
end
|
1106
|
+
|
1107
|
+
class String
|
1108
|
+
def to_result(cx, *args)
|
1109
|
+
args.shift.to_result(cx, *args)
|
1110
|
+
self
|
1111
|
+
end
|
1112
|
+
end
|
1113
|
+
|
1114
|
+
class Array
|
1115
|
+
def to_result(cx, *args)
|
1116
|
+
self.shift.to_result(cx, *self)
|
1117
|
+
end
|
1118
|
+
end
|
1119
|
+
|
1120
|
+
class Symbol
|
1121
|
+
def to_result(cx, *args)
|
1122
|
+
cx.send(self, *args)
|
1123
|
+
end
|
1124
|
+
end
|
1125
|
+
|
1126
|
+
class Fixnum
|
1127
|
+
def to_result(cx, *args)
|
1128
|
+
cx.status self
|
1129
|
+
args.shift.to_result(cx, *args)
|
1130
|
+
end
|
1131
|
+
end
|
1132
|
+
|
1133
|
+
class NilClass
|
1134
|
+
def to_result(cx, *args)
|
1135
|
+
''
|
1136
|
+
end
|
1137
|
+
end
|
44
1138
|
|
45
1139
|
at_exit do
|
46
|
-
|
47
|
-
|
48
|
-
|
1140
|
+
raise $! if $!
|
1141
|
+
if Sinatra.application.options.run
|
1142
|
+
Sinatra.run
|
1143
|
+
end
|
49
1144
|
end
|
1145
|
+
|
1146
|
+
mime :xml, 'application/xml'
|
1147
|
+
mime :js, 'application/javascript'
|