darkhelmet-sinatra 0.9.0.5
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/AUTHORS +41 -0
- data/CHANGES +243 -0
- data/LICENSE +22 -0
- data/README.rdoc +535 -0
- data/Rakefile +136 -0
- data/compat/app_test.rb +301 -0
- data/compat/application_test.rb +334 -0
- data/compat/builder_test.rb +101 -0
- data/compat/compat_test.rb +12 -0
- data/compat/custom_error_test.rb +62 -0
- data/compat/erb_test.rb +136 -0
- data/compat/events_test.rb +78 -0
- data/compat/filter_test.rb +30 -0
- data/compat/haml_test.rb +233 -0
- data/compat/helper.rb +30 -0
- data/compat/mapped_error_test.rb +72 -0
- data/compat/pipeline_test.rb +71 -0
- data/compat/public/foo.xml +1 -0
- data/compat/sass_test.rb +57 -0
- data/compat/sessions_test.rb +39 -0
- data/compat/streaming_test.rb +133 -0
- data/compat/sym_params_test.rb +19 -0
- data/compat/template_test.rb +30 -0
- data/compat/use_in_file_templates_test.rb +47 -0
- data/compat/views/foo.builder +1 -0
- data/compat/views/foo.erb +1 -0
- data/compat/views/foo.haml +1 -0
- data/compat/views/foo.sass +2 -0
- data/compat/views/foo_layout.erb +2 -0
- data/compat/views/foo_layout.haml +2 -0
- data/compat/views/layout_test/foo.builder +1 -0
- data/compat/views/layout_test/foo.erb +1 -0
- data/compat/views/layout_test/foo.haml +1 -0
- data/compat/views/layout_test/foo.sass +2 -0
- data/compat/views/layout_test/layout.builder +3 -0
- data/compat/views/layout_test/layout.erb +1 -0
- data/compat/views/layout_test/layout.haml +1 -0
- data/compat/views/layout_test/layout.sass +2 -0
- data/compat/views/no_layout/no_layout.builder +1 -0
- data/compat/views/no_layout/no_layout.haml +1 -0
- data/lib/sinatra/base.rb +1007 -0
- data/lib/sinatra/compat.rb +252 -0
- data/lib/sinatra/images/404.png +0 -0
- data/lib/sinatra/images/500.png +0 -0
- data/lib/sinatra/main.rb +47 -0
- data/lib/sinatra/test/bacon.rb +19 -0
- data/lib/sinatra/test/rspec.rb +13 -0
- data/lib/sinatra/test/spec.rb +11 -0
- data/lib/sinatra/test/unit.rb +13 -0
- data/lib/sinatra/test.rb +121 -0
- data/lib/sinatra.rb +8 -0
- data/sinatra.gemspec +116 -0
- data/test/base_test.rb +112 -0
- data/test/builder_test.rb +64 -0
- data/test/data/reload_app_file.rb +3 -0
- data/test/erb_test.rb +81 -0
- data/test/extensions_test.rb +63 -0
- data/test/filter_test.rb +99 -0
- data/test/haml_test.rb +68 -0
- data/test/helper.rb +85 -0
- data/test/helpers_test.rb +467 -0
- data/test/mapped_error_test.rb +160 -0
- data/test/middleware_test.rb +60 -0
- data/test/options_test.rb +374 -0
- data/test/reload_test.rb +68 -0
- data/test/request_test.rb +18 -0
- data/test/response_test.rb +42 -0
- data/test/result_test.rb +98 -0
- data/test/routing_test.rb +712 -0
- data/test/sass_test.rb +36 -0
- data/test/server_test.rb +41 -0
- data/test/sinatra_test.rb +13 -0
- data/test/static_test.rb +65 -0
- data/test/templates_test.rb +88 -0
- data/test/test_test.rb +109 -0
- data/test/views/hello.builder +1 -0
- data/test/views/hello.erb +1 -0
- data/test/views/hello.haml +1 -0
- data/test/views/hello.sass +2 -0
- data/test/views/hello.test +1 -0
- data/test/views/layout2.builder +3 -0
- data/test/views/layout2.erb +2 -0
- data/test/views/layout2.haml +2 -0
- data/test/views/layout2.test +1 -0
- metadata +184 -0
@@ -0,0 +1,47 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/helper'
|
2
|
+
|
3
|
+
context "Rendering in file templates" do
|
4
|
+
|
5
|
+
setup do
|
6
|
+
Sinatra.application = nil
|
7
|
+
use_in_file_templates!
|
8
|
+
end
|
9
|
+
|
10
|
+
specify "should set template" do
|
11
|
+
assert Sinatra.application.templates[:foo]
|
12
|
+
end
|
13
|
+
|
14
|
+
specify "should set layout" do
|
15
|
+
assert Sinatra.application.templates[:layout]
|
16
|
+
end
|
17
|
+
|
18
|
+
specify "should render without layout if specified" do
|
19
|
+
get '/' do
|
20
|
+
haml :foo, :layout => false
|
21
|
+
end
|
22
|
+
|
23
|
+
get_it '/'
|
24
|
+
assert_equal "this is foo\n", body
|
25
|
+
end
|
26
|
+
|
27
|
+
specify "should render with layout if specified" do
|
28
|
+
get '/' do
|
29
|
+
haml :foo
|
30
|
+
end
|
31
|
+
|
32
|
+
get_it '/'
|
33
|
+
assert_equal "X\nthis is foo\nX\n", body
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
__END__
|
39
|
+
|
40
|
+
@@ foo
|
41
|
+
this is foo
|
42
|
+
|
43
|
+
@@ layout
|
44
|
+
X
|
45
|
+
= yield
|
46
|
+
X
|
47
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
xml.exclaim "You rock #{@name}!"
|
@@ -0,0 +1 @@
|
|
1
|
+
You rock <%= @name %>!
|
@@ -0,0 +1 @@
|
|
1
|
+
== You rock #{@name}!
|
@@ -0,0 +1 @@
|
|
1
|
+
xml.this "is foo!"
|
@@ -0,0 +1 @@
|
|
1
|
+
This is foo!
|
@@ -0,0 +1 @@
|
|
1
|
+
This is foo!
|
@@ -0,0 +1 @@
|
|
1
|
+
x <%= yield %> x
|
@@ -0,0 +1 @@
|
|
1
|
+
== x #{yield} x
|
@@ -0,0 +1 @@
|
|
1
|
+
xml.foo "No Layout!"
|
@@ -0,0 +1 @@
|
|
1
|
+
%h1 No Layout!
|
data/lib/sinatra/base.rb
ADDED
@@ -0,0 +1,1007 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'time'
|
3
|
+
require 'uri'
|
4
|
+
require 'rack'
|
5
|
+
require 'rack/builder'
|
6
|
+
require 'colored'
|
7
|
+
|
8
|
+
module Sinatra
|
9
|
+
VERSION = '0.9.0.5'
|
10
|
+
|
11
|
+
# The request object. See Rack::Request for more info:
|
12
|
+
# http://rack.rubyforge.org/doc/classes/Rack/Request.html
|
13
|
+
class Request < Rack::Request
|
14
|
+
def user_agent
|
15
|
+
@env['HTTP_USER_AGENT']
|
16
|
+
end
|
17
|
+
|
18
|
+
def accept
|
19
|
+
@env['HTTP_ACCEPT'].to_s.split(',').map { |a| a.strip }
|
20
|
+
end
|
21
|
+
|
22
|
+
# Override Rack 0.9.x's #params implementation (see #72 in lighthouse)
|
23
|
+
def params
|
24
|
+
self.GET.update(self.POST)
|
25
|
+
rescue EOFError => boom
|
26
|
+
self.GET
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# The response object. See Rack::Response and Rack::ResponseHelpers for
|
31
|
+
# more info:
|
32
|
+
# http://rack.rubyforge.org/doc/classes/Rack/Response.html
|
33
|
+
# http://rack.rubyforge.org/doc/classes/Rack/Response/Helpers.html
|
34
|
+
class Response < Rack::Response
|
35
|
+
def initialize
|
36
|
+
@status, @body = 200, []
|
37
|
+
@header = Rack::Utils::HeaderHash.new({'Content-Type' => 'text/html'})
|
38
|
+
end
|
39
|
+
|
40
|
+
def write(str)
|
41
|
+
@body << str.to_s
|
42
|
+
str
|
43
|
+
end
|
44
|
+
|
45
|
+
def finish
|
46
|
+
@body = block if block_given?
|
47
|
+
if [204, 304].include?(status.to_i)
|
48
|
+
header.delete "Content-Type"
|
49
|
+
[status.to_i, header.to_hash, []]
|
50
|
+
else
|
51
|
+
body = @body || []
|
52
|
+
body = [body] if body.respond_to? :to_str
|
53
|
+
if header["Content-Length"].nil? && body.respond_to?(:to_ary)
|
54
|
+
header["Content-Length"] = body.to_ary.
|
55
|
+
inject(0) { |len, part| len + part.bytesize }.to_s
|
56
|
+
end
|
57
|
+
[status.to_i, header.to_hash, body]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class NotFound < NameError #:nodoc:
|
63
|
+
def code ; 404 ; end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Methods available to routes, before filters, and views.
|
67
|
+
module Helpers
|
68
|
+
# Set or retrieve the response status code.
|
69
|
+
def status(value=nil)
|
70
|
+
response.status = value if value
|
71
|
+
response.status
|
72
|
+
end
|
73
|
+
|
74
|
+
# Set or retrieve the response body. When a block is given,
|
75
|
+
# evaluation is deferred until the body is read with #each.
|
76
|
+
def body(value=nil, &block)
|
77
|
+
if block_given?
|
78
|
+
def block.each ; yield call ; end
|
79
|
+
response.body = block
|
80
|
+
else
|
81
|
+
response.body = value
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Halt processing and redirect to the URI provided.
|
86
|
+
def redirect(uri, *args)
|
87
|
+
status 302
|
88
|
+
response['Location'] = uri
|
89
|
+
halt(*args)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Halt processing and return the error status provided.
|
93
|
+
def error(code, body=nil)
|
94
|
+
code, body = 500, code.to_str if code.respond_to? :to_str
|
95
|
+
response.body = body unless body.nil?
|
96
|
+
halt code
|
97
|
+
end
|
98
|
+
|
99
|
+
# Halt processing and return a 404 Not Found.
|
100
|
+
def not_found(body=nil)
|
101
|
+
error 404, body
|
102
|
+
end
|
103
|
+
|
104
|
+
# Access the underlying Rack session.
|
105
|
+
def session
|
106
|
+
env['rack.session'] ||= {}
|
107
|
+
end
|
108
|
+
|
109
|
+
# Look up a media type by file extension in Rack's mime registry.
|
110
|
+
def media_type(type)
|
111
|
+
Base.media_type(type)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Set the Content-Type of the response body given a media type or file
|
115
|
+
# extension.
|
116
|
+
def content_type(type, params={})
|
117
|
+
media_type = self.media_type(type)
|
118
|
+
fail "Unknown media type: %p" % type if media_type.nil?
|
119
|
+
if params.any?
|
120
|
+
params = params.collect { |kv| "%s=%s" % kv }.join(', ')
|
121
|
+
response['Content-Type'] = [media_type, params].join(";")
|
122
|
+
else
|
123
|
+
response['Content-Type'] = media_type
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# Set the Content-Disposition to "attachment" with the specified filename,
|
128
|
+
# instructing the user agents to prompt to save.
|
129
|
+
def attachment(filename=nil)
|
130
|
+
response['Content-Disposition'] = 'attachment'
|
131
|
+
if filename
|
132
|
+
params = '; filename="%s"' % File.basename(filename)
|
133
|
+
response['Content-Disposition'] << params
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# Use the contents of the file at +path+ as the response body.
|
138
|
+
def send_file(path, opts={})
|
139
|
+
stat = File.stat(path)
|
140
|
+
last_modified stat.mtime
|
141
|
+
|
142
|
+
content_type media_type(opts[:type]) ||
|
143
|
+
media_type(File.extname(path)) ||
|
144
|
+
response['Content-Type'] ||
|
145
|
+
'application/octet-stream'
|
146
|
+
|
147
|
+
response['Content-Length'] ||= (opts[:length] || stat.size).to_s
|
148
|
+
|
149
|
+
if opts[:disposition] == 'attachment' || opts[:filename]
|
150
|
+
attachment opts[:filename] || path
|
151
|
+
elsif opts[:disposition] == 'inline'
|
152
|
+
response['Content-Disposition'] = 'inline'
|
153
|
+
end
|
154
|
+
|
155
|
+
halt StaticFile.open(path, 'rb')
|
156
|
+
rescue Errno::ENOENT
|
157
|
+
not_found
|
158
|
+
end
|
159
|
+
|
160
|
+
class StaticFile < ::File #:nodoc:
|
161
|
+
alias_method :to_path, :path
|
162
|
+
def each
|
163
|
+
rewind
|
164
|
+
while buf = read(8192)
|
165
|
+
yield buf
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
# Set the last modified time of the resource (HTTP 'Last-Modified' header)
|
171
|
+
# and halt if conditional GET matches. The +time+ argument is a Time,
|
172
|
+
# DateTime, or other object that responds to +to_time+.
|
173
|
+
#
|
174
|
+
# When the current request includes an 'If-Modified-Since' header that
|
175
|
+
# matches the time specified, execution is immediately halted with a
|
176
|
+
# '304 Not Modified' response.
|
177
|
+
def last_modified(time)
|
178
|
+
time = time.to_time if time.respond_to?(:to_time)
|
179
|
+
time = time.httpdate if time.respond_to?(:httpdate)
|
180
|
+
response['Last-Modified'] = time
|
181
|
+
halt 304 if time == request.env['HTTP_IF_MODIFIED_SINCE']
|
182
|
+
time
|
183
|
+
end
|
184
|
+
|
185
|
+
# Set the response entity tag (HTTP 'ETag' header) and halt if conditional
|
186
|
+
# GET matches. The +value+ argument is an identifier that uniquely
|
187
|
+
# identifies the current version of the resource. The +strength+ argument
|
188
|
+
# indicates whether the etag should be used as a :strong (default) or :weak
|
189
|
+
# cache validator.
|
190
|
+
#
|
191
|
+
# When the current request includes an 'If-None-Match' header with a
|
192
|
+
# matching etag, execution is immediately halted. If the request method is
|
193
|
+
# GET or HEAD, a '304 Not Modified' response is sent.
|
194
|
+
def etag(value, kind=:strong)
|
195
|
+
raise TypeError, ":strong or :weak expected" if ![:strong,:weak].include?(kind)
|
196
|
+
value = '"%s"' % value
|
197
|
+
value = 'W/' + value if kind == :weak
|
198
|
+
response['ETag'] = value
|
199
|
+
|
200
|
+
# Conditional GET check
|
201
|
+
if etags = env['HTTP_IF_NONE_MATCH']
|
202
|
+
etags = etags.split(/\s*,\s*/)
|
203
|
+
halt 304 if etags.include?(value) || etags.include?('*')
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
## Sugar for redirect (example: redirect back)
|
208
|
+
def back ; request.referer ; end
|
209
|
+
|
210
|
+
end
|
211
|
+
|
212
|
+
# Template rendering methods. Each method takes a the name of a template
|
213
|
+
# to render as a Symbol and returns a String with the rendered output.
|
214
|
+
module Templates
|
215
|
+
def erb(template, options={})
|
216
|
+
require 'erb' unless defined? ::ERB
|
217
|
+
render :erb, template, options
|
218
|
+
end
|
219
|
+
|
220
|
+
def haml(template, options={})
|
221
|
+
require 'haml' unless defined? ::Haml
|
222
|
+
options[:options] ||= self.class.haml if self.class.respond_to? :haml
|
223
|
+
render :haml, template, options
|
224
|
+
end
|
225
|
+
|
226
|
+
def sass(template, options={}, &block)
|
227
|
+
require 'sass' unless defined? ::Sass
|
228
|
+
options[:layout] = false
|
229
|
+
render :sass, template, options
|
230
|
+
end
|
231
|
+
|
232
|
+
def builder(template=nil, options={}, &block)
|
233
|
+
require 'builder' unless defined? ::Builder
|
234
|
+
options, template = template, nil if template.is_a?(Hash)
|
235
|
+
template = lambda { block } if template.nil?
|
236
|
+
render :builder, template, options
|
237
|
+
end
|
238
|
+
|
239
|
+
private
|
240
|
+
def render(engine, template, options={}) #:nodoc:
|
241
|
+
data = lookup_template(engine, template, options)
|
242
|
+
output = __send__("render_#{engine}", template, data, options)
|
243
|
+
layout, data = lookup_layout(engine, options)
|
244
|
+
if layout
|
245
|
+
__send__("render_#{engine}", layout, data, options) { output }
|
246
|
+
else
|
247
|
+
output
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
def lookup_template(engine, template, options={})
|
252
|
+
case template
|
253
|
+
when Symbol
|
254
|
+
if cached = self.class.templates[template]
|
255
|
+
lookup_template(engine, cached, options)
|
256
|
+
else
|
257
|
+
::File.read(template_path(engine, template, options))
|
258
|
+
end
|
259
|
+
when Proc
|
260
|
+
template.call
|
261
|
+
when String
|
262
|
+
template
|
263
|
+
else
|
264
|
+
raise ArgumentError
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
def lookup_layout(engine, options)
|
269
|
+
return if options[:layout] == false
|
270
|
+
options.delete(:layout) if options[:layout] == true
|
271
|
+
template = options[:layout] || :layout
|
272
|
+
data = lookup_template(engine, template, options)
|
273
|
+
[template, data]
|
274
|
+
rescue Errno::ENOENT
|
275
|
+
nil
|
276
|
+
end
|
277
|
+
|
278
|
+
def template_path(engine, template, options={})
|
279
|
+
views_dir =
|
280
|
+
options[:views_directory] || self.options.views || "./views"
|
281
|
+
"#{views_dir}/#{template}.#{engine}"
|
282
|
+
end
|
283
|
+
|
284
|
+
def render_erb(template, data, options, &block)
|
285
|
+
original_out_buf = @_out_buf
|
286
|
+
data = data.call if data.kind_of? Proc
|
287
|
+
|
288
|
+
instance = ::ERB.new(data, nil, nil, '@_out_buf')
|
289
|
+
locals = options[:locals] || {}
|
290
|
+
locals_assigns = locals.to_a.collect { |k,v| "#{k} = locals[:#{k}]" }
|
291
|
+
|
292
|
+
src = "#{locals_assigns.join("\n")}\n#{instance.src}"
|
293
|
+
eval src, binding, '(__ERB__)', locals_assigns.length + 1
|
294
|
+
@_out_buf, result = original_out_buf, @_out_buf
|
295
|
+
result
|
296
|
+
end
|
297
|
+
|
298
|
+
def render_haml(template, data, options, &block)
|
299
|
+
engine = ::Haml::Engine.new(data, options[:options] || {})
|
300
|
+
engine.render(self, options[:locals] || {}, &block)
|
301
|
+
end
|
302
|
+
|
303
|
+
def render_sass(template, data, options, &block)
|
304
|
+
engine = ::Sass::Engine.new(data, options[:sass] || {})
|
305
|
+
engine.render
|
306
|
+
end
|
307
|
+
|
308
|
+
def render_builder(template, data, options, &block)
|
309
|
+
xml = ::Builder::XmlMarkup.new(:indent => 2)
|
310
|
+
if data.respond_to?(:to_str)
|
311
|
+
eval data.to_str, binding, '<BUILDER>', 1
|
312
|
+
elsif data.kind_of?(Proc)
|
313
|
+
data.call(xml)
|
314
|
+
end
|
315
|
+
xml.target!
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
# Base class for all Sinatra applications and middleware.
|
320
|
+
class Base
|
321
|
+
include Rack::Utils
|
322
|
+
include Helpers
|
323
|
+
include Templates
|
324
|
+
|
325
|
+
attr_accessor :app
|
326
|
+
|
327
|
+
def initialize(app=nil)
|
328
|
+
@app = app
|
329
|
+
yield self if block_given?
|
330
|
+
end
|
331
|
+
|
332
|
+
# Rack call interface.
|
333
|
+
def call(env)
|
334
|
+
dup.call!(env)
|
335
|
+
end
|
336
|
+
|
337
|
+
attr_accessor :env, :request, :response, :params
|
338
|
+
|
339
|
+
def call!(env)
|
340
|
+
@env = env
|
341
|
+
@request = Request.new(env)
|
342
|
+
@response = Response.new
|
343
|
+
@params = nil
|
344
|
+
|
345
|
+
invoke { dispatch! }
|
346
|
+
invoke { error_block!(response.status) }
|
347
|
+
|
348
|
+
# never respond with a body on HEAD requests
|
349
|
+
@response.body = [] if @env['REQUEST_METHOD'] == 'HEAD'
|
350
|
+
|
351
|
+
@response.finish
|
352
|
+
end
|
353
|
+
|
354
|
+
# Access options defined with Base.set.
|
355
|
+
def options
|
356
|
+
self.class
|
357
|
+
end
|
358
|
+
|
359
|
+
# Exit the current block and halt the response.
|
360
|
+
def halt(*response)
|
361
|
+
response = response.first if response.length == 1
|
362
|
+
throw :halt, response
|
363
|
+
end
|
364
|
+
|
365
|
+
# Pass control to the next matching route.
|
366
|
+
def pass
|
367
|
+
throw :pass
|
368
|
+
end
|
369
|
+
|
370
|
+
# Forward the request to the downstream app -- middleware only.
|
371
|
+
def forward
|
372
|
+
fail "downstream app not set" unless @app.respond_to? :call
|
373
|
+
status, headers, body = @app.call(@request.env)
|
374
|
+
@response.status = status
|
375
|
+
@response.body = body
|
376
|
+
headers.each { |k, v| @response[k] = v }
|
377
|
+
nil
|
378
|
+
end
|
379
|
+
|
380
|
+
private
|
381
|
+
# Run before filters and then locate and run a matching route.
|
382
|
+
def route!
|
383
|
+
@params = nested_params(@request.params)
|
384
|
+
|
385
|
+
# before filters
|
386
|
+
self.class.filters.each { |block| instance_eval(&block) }
|
387
|
+
|
388
|
+
# routes
|
389
|
+
if routes = self.class.routes[@request.request_method]
|
390
|
+
original_params = @params
|
391
|
+
path = unescape(@request.path_info)
|
392
|
+
|
393
|
+
routes.each do |pattern, rpath, keys, conditions, block|
|
394
|
+
if match = pattern.match(path)
|
395
|
+
@env['rack.errors'].write("=== Path '#{path}' matched route '#{rpath}'\n".green)
|
396
|
+
values = match.captures.to_a
|
397
|
+
params =
|
398
|
+
if keys.any?
|
399
|
+
keys.zip(values).inject({}) do |hash,(k,v)|
|
400
|
+
if k == 'splat'
|
401
|
+
(hash[k] ||= []) << v
|
402
|
+
else
|
403
|
+
hash[k] = v
|
404
|
+
end
|
405
|
+
hash
|
406
|
+
end
|
407
|
+
elsif values.any?
|
408
|
+
{'captures' => values}
|
409
|
+
else
|
410
|
+
{}
|
411
|
+
end
|
412
|
+
@params = original_params.merge(params)
|
413
|
+
@block_params = values
|
414
|
+
|
415
|
+
catch(:pass) do
|
416
|
+
conditions.each { |cond|
|
417
|
+
throw :pass if instance_eval(&cond) == false }
|
418
|
+
throw :halt, instance_eval(&block)
|
419
|
+
end
|
420
|
+
else
|
421
|
+
@env['rack.errors'].write("=== Path '#{path}' failed route '#{rpath}'\n".red)
|
422
|
+
end
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
# No matching route found or all routes passed -- forward downstream
|
427
|
+
# when running as middleware; 404 when running as normal app.
|
428
|
+
if @app
|
429
|
+
forward
|
430
|
+
else
|
431
|
+
raise NotFound
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
def nested_params(params)
|
436
|
+
return indifferent_hash.merge(params) if !params.keys.join.include?('[')
|
437
|
+
params.inject indifferent_hash do |res, (key,val)|
|
438
|
+
if key.include?('[')
|
439
|
+
head = key.split(/[\]\[]+/)
|
440
|
+
last = head.pop
|
441
|
+
head.inject(res){ |hash,k| hash[k] ||= indifferent_hash }[last] = val
|
442
|
+
else
|
443
|
+
res[key] = val
|
444
|
+
end
|
445
|
+
res
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
449
|
+
def indifferent_hash
|
450
|
+
Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
|
451
|
+
end
|
452
|
+
|
453
|
+
# Run the block with 'throw :halt' support and apply result to the response.
|
454
|
+
def invoke(&block)
|
455
|
+
res = catch(:halt) { instance_eval(&block) }
|
456
|
+
return if res.nil?
|
457
|
+
|
458
|
+
case
|
459
|
+
when res.respond_to?(:to_str)
|
460
|
+
@response.body = [res]
|
461
|
+
when res.respond_to?(:to_ary)
|
462
|
+
res = res.to_ary
|
463
|
+
if Fixnum === res.first
|
464
|
+
if res.length == 3
|
465
|
+
@response.status, headers, body = res
|
466
|
+
@response.body = body if body
|
467
|
+
headers.each { |k, v| @response.headers[k] = v } if headers
|
468
|
+
elsif res.length == 2
|
469
|
+
@response.status = res.first
|
470
|
+
@response.body = res.last
|
471
|
+
else
|
472
|
+
raise TypeError, "#{res.inspect} not supported"
|
473
|
+
end
|
474
|
+
else
|
475
|
+
@response.body = res
|
476
|
+
end
|
477
|
+
when res.respond_to?(:each)
|
478
|
+
@response.body = res
|
479
|
+
when (100...599) === res
|
480
|
+
@response.status = res
|
481
|
+
end
|
482
|
+
|
483
|
+
res
|
484
|
+
end
|
485
|
+
|
486
|
+
# Dispatch a request with error handling.
|
487
|
+
def dispatch!
|
488
|
+
route!
|
489
|
+
rescue NotFound => boom
|
490
|
+
handle_not_found!(boom)
|
491
|
+
rescue ::Exception => boom
|
492
|
+
handle_exception!(boom)
|
493
|
+
end
|
494
|
+
|
495
|
+
def handle_not_found!(boom)
|
496
|
+
@env['sinatra.error'] = boom
|
497
|
+
@response.status = 404
|
498
|
+
@response.body = ['<h1>Not Found</h1>']
|
499
|
+
error_block! boom.class, NotFound
|
500
|
+
end
|
501
|
+
|
502
|
+
def handle_exception!(boom)
|
503
|
+
@env['sinatra.error'] = boom
|
504
|
+
|
505
|
+
dump_errors!(boom) if options.dump_errors?
|
506
|
+
raise boom if options.raise_errors?
|
507
|
+
|
508
|
+
@response.status = 500
|
509
|
+
error_block! boom.class, Exception
|
510
|
+
end
|
511
|
+
|
512
|
+
# Find an custom error block for the key(s) specified.
|
513
|
+
def error_block!(*keys)
|
514
|
+
errmap = self.class.errors
|
515
|
+
keys.each do |key|
|
516
|
+
if block = errmap[key]
|
517
|
+
res = instance_eval(&block)
|
518
|
+
return res
|
519
|
+
end
|
520
|
+
end
|
521
|
+
nil
|
522
|
+
end
|
523
|
+
|
524
|
+
def dump_errors!(boom)
|
525
|
+
backtrace = clean_backtrace(boom.backtrace)
|
526
|
+
msg = ["#{boom.class} - #{boom.message}:",
|
527
|
+
*backtrace].join("\n ")
|
528
|
+
@env['rack.errors'].write(msg)
|
529
|
+
end
|
530
|
+
|
531
|
+
def clean_backtrace(trace)
|
532
|
+
return trace unless options.clean_trace?
|
533
|
+
|
534
|
+
trace.reject { |line|
|
535
|
+
line =~ /lib\/sinatra.*\.rb/ ||
|
536
|
+
(defined?(Gem) && line.include?(Gem.dir))
|
537
|
+
}.map! { |line| line.gsub(/^\.\//, '') }
|
538
|
+
end
|
539
|
+
|
540
|
+
@routes = {}
|
541
|
+
@filters = []
|
542
|
+
@conditions = []
|
543
|
+
@templates = {}
|
544
|
+
@middleware = []
|
545
|
+
@callsite = nil
|
546
|
+
@errors = {}
|
547
|
+
|
548
|
+
class << self
|
549
|
+
attr_accessor :routes, :filters, :conditions, :templates,
|
550
|
+
:middleware, :errors
|
551
|
+
|
552
|
+
public
|
553
|
+
def set(option, value=self)
|
554
|
+
if value.kind_of?(Proc)
|
555
|
+
metadef(option, &value)
|
556
|
+
metadef("#{option}?") { !!__send__(option) }
|
557
|
+
metadef("#{option}=") { |val| set(option, Proc.new{val}) }
|
558
|
+
elsif value == self && option.respond_to?(:to_hash)
|
559
|
+
option.to_hash.each { |k,v| set(k, v) }
|
560
|
+
elsif respond_to?("#{option}=")
|
561
|
+
__send__ "#{option}=", value
|
562
|
+
else
|
563
|
+
set option, Proc.new{value}
|
564
|
+
end
|
565
|
+
self
|
566
|
+
end
|
567
|
+
|
568
|
+
def enable(*opts)
|
569
|
+
opts.each { |key| set(key, true) }
|
570
|
+
end
|
571
|
+
|
572
|
+
def disable(*opts)
|
573
|
+
opts.each { |key| set(key, false) }
|
574
|
+
end
|
575
|
+
|
576
|
+
def error(codes=Exception, &block)
|
577
|
+
if codes.respond_to? :each
|
578
|
+
codes.each { |err| error(err, &block) }
|
579
|
+
else
|
580
|
+
@errors[codes] = block
|
581
|
+
end
|
582
|
+
end
|
583
|
+
|
584
|
+
def not_found(&block)
|
585
|
+
error 404, &block
|
586
|
+
end
|
587
|
+
|
588
|
+
def template(name, &block)
|
589
|
+
templates[name] = block
|
590
|
+
end
|
591
|
+
|
592
|
+
def layout(name=:layout, &block)
|
593
|
+
template name, &block
|
594
|
+
end
|
595
|
+
|
596
|
+
def use_in_file_templates!
|
597
|
+
ignore = [/lib\/sinatra.*\.rb/, /\(.*\)/, /rubygems\/custom_require\.rb/]
|
598
|
+
file = caller.
|
599
|
+
map { |line| line.sub(/:\d+.*$/, '') }.
|
600
|
+
find { |line| ignore.all? { |pattern| line !~ pattern } }
|
601
|
+
if data = ::IO.read(file).split('__END__')[1]
|
602
|
+
data.gsub!(/\r\n/, "\n")
|
603
|
+
template = nil
|
604
|
+
data.each_line do |line|
|
605
|
+
if line =~ /^@@\s*(.*)/
|
606
|
+
template = templates[$1.to_sym] = ''
|
607
|
+
elsif template
|
608
|
+
template << line
|
609
|
+
end
|
610
|
+
end
|
611
|
+
end
|
612
|
+
end
|
613
|
+
|
614
|
+
# Look up a media type by file extension in Rack's mime registry.
|
615
|
+
def media_type(type)
|
616
|
+
return type if type.nil? || type.to_s.include?('/')
|
617
|
+
type = ".#{type}" unless type.to_s[0] == ?.
|
618
|
+
Rack::Mime.mime_type(type, nil)
|
619
|
+
end
|
620
|
+
|
621
|
+
def before(&block)
|
622
|
+
@filters << block
|
623
|
+
end
|
624
|
+
|
625
|
+
def condition(&block)
|
626
|
+
@conditions << block
|
627
|
+
end
|
628
|
+
|
629
|
+
private
|
630
|
+
def host_name(pattern)
|
631
|
+
condition { pattern === request.host }
|
632
|
+
end
|
633
|
+
|
634
|
+
def user_agent(pattern)
|
635
|
+
condition {
|
636
|
+
if request.user_agent =~ pattern
|
637
|
+
@params[:agent] = $~[1..-1]
|
638
|
+
true
|
639
|
+
else
|
640
|
+
false
|
641
|
+
end
|
642
|
+
}
|
643
|
+
end
|
644
|
+
|
645
|
+
def accept_mime_types(types)
|
646
|
+
types = [types] unless types.kind_of? Array
|
647
|
+
types.map!{|t| media_type(t)}
|
648
|
+
|
649
|
+
condition {
|
650
|
+
matching_types = (request.accept & types)
|
651
|
+
unless matching_types.empty?
|
652
|
+
response.headers['Content-Type'] = matching_types.first
|
653
|
+
true
|
654
|
+
else
|
655
|
+
false
|
656
|
+
end
|
657
|
+
}
|
658
|
+
end
|
659
|
+
|
660
|
+
public
|
661
|
+
def get(path, opts={}, &block)
|
662
|
+
conditions = @conditions.dup
|
663
|
+
route('GET', path, opts, &block)
|
664
|
+
|
665
|
+
@conditions = conditions
|
666
|
+
route('HEAD', path, opts, &block)
|
667
|
+
end
|
668
|
+
|
669
|
+
def put(path, opts={}, &bk); route 'PUT', path, opts, &bk; end
|
670
|
+
def post(path, opts={}, &bk); route 'POST', path, opts, &bk; end
|
671
|
+
def delete(path, opts={}, &bk); route 'DELETE', path, opts, &bk; end
|
672
|
+
def head(path, opts={}, &bk); route 'HEAD', path, opts, &bk; end
|
673
|
+
|
674
|
+
private
|
675
|
+
def route(verb, path, opts={}, &block)
|
676
|
+
host_name opts[:host] if opts.key?(:host)
|
677
|
+
user_agent opts[:agent] if opts.key?(:agent)
|
678
|
+
accept_mime_types opts[:provides] if opts.key?(:provides)
|
679
|
+
|
680
|
+
pattern, keys = compile(path)
|
681
|
+
conditions, @conditions = @conditions, []
|
682
|
+
|
683
|
+
define_method "#{verb} #{path}", &block
|
684
|
+
unbound_method = instance_method("#{verb} #{path}")
|
685
|
+
block =
|
686
|
+
if block.arity != 0
|
687
|
+
lambda { unbound_method.bind(self).call(*@block_params) }
|
688
|
+
else
|
689
|
+
lambda { unbound_method.bind(self).call }
|
690
|
+
end
|
691
|
+
|
692
|
+
(routes[verb] ||= []).
|
693
|
+
push([pattern, path, keys, conditions, block]).last
|
694
|
+
end
|
695
|
+
|
696
|
+
def compile(path)
|
697
|
+
keys = []
|
698
|
+
if path.respond_to? :to_str
|
699
|
+
special_chars = %w{. + ( )}
|
700
|
+
pattern =
|
701
|
+
path.gsub(/((:\w+)|[\*#{special_chars.join}])/) do |match|
|
702
|
+
case match
|
703
|
+
when "*"
|
704
|
+
keys << 'splat'
|
705
|
+
"(.*?)"
|
706
|
+
when *special_chars
|
707
|
+
Regexp.escape(match)
|
708
|
+
else
|
709
|
+
keys << $2[1..-1]
|
710
|
+
"([^/?&#]+)"
|
711
|
+
end
|
712
|
+
end
|
713
|
+
[/^#{pattern}$/, keys]
|
714
|
+
elsif path.respond_to? :match
|
715
|
+
[path, keys]
|
716
|
+
else
|
717
|
+
raise TypeError, path
|
718
|
+
end
|
719
|
+
end
|
720
|
+
|
721
|
+
public
|
722
|
+
def helpers(*extensions, &block)
|
723
|
+
class_eval(&block) if block_given?
|
724
|
+
include *extensions
|
725
|
+
end
|
726
|
+
|
727
|
+
def register(*extensions, &block)
|
728
|
+
extensions << Module.new(&block) if block
|
729
|
+
extend *extensions
|
730
|
+
end
|
731
|
+
|
732
|
+
def development? ; environment == :development ; end
|
733
|
+
def test? ; environment == :test ; end
|
734
|
+
def production? ; environment == :production ; end
|
735
|
+
|
736
|
+
def configure(*envs, &block)
|
737
|
+
return if reloading?
|
738
|
+
yield if envs.empty? || envs.include?(environment.to_sym)
|
739
|
+
end
|
740
|
+
|
741
|
+
def use(middleware, *args, &block)
|
742
|
+
reset_middleware
|
743
|
+
@middleware << [middleware, args, block]
|
744
|
+
end
|
745
|
+
|
746
|
+
def run!(options={})
|
747
|
+
set options
|
748
|
+
handler = detect_rack_handler
|
749
|
+
handler_name = handler.name.gsub(/.*::/, '')
|
750
|
+
puts "== Sinatra/#{Sinatra::VERSION} has taken the stage " +
|
751
|
+
"on #{port} for #{environment} with backup from #{handler_name}" unless handler_name =~/cgi/i
|
752
|
+
handler.run self, :Host => host, :Port => port do |server|
|
753
|
+
trap(:INT) do
|
754
|
+
## Use thins' hard #stop! if available, otherwise just #stop
|
755
|
+
server.respond_to?(:stop!) ? server.stop! : server.stop
|
756
|
+
puts "\n== Sinatra has ended his set (crowd applauds)" unless handler_name =~/cgi/i
|
757
|
+
end
|
758
|
+
end
|
759
|
+
rescue Errno::EADDRINUSE => e
|
760
|
+
puts "== Someone is already performing on port #{port}!"
|
761
|
+
end
|
762
|
+
|
763
|
+
def call(env)
|
764
|
+
synchronize do
|
765
|
+
reload! if reload?
|
766
|
+
construct_middleware if @callsite.nil?
|
767
|
+
@callsite.call(env)
|
768
|
+
end
|
769
|
+
end
|
770
|
+
|
771
|
+
def reload!
|
772
|
+
@reloading = true
|
773
|
+
superclass.send :reset!, self
|
774
|
+
$LOADED_FEATURES.delete("sinatra.rb")
|
775
|
+
::Kernel.load app_file
|
776
|
+
@reloading = false
|
777
|
+
end
|
778
|
+
|
779
|
+
private
|
780
|
+
def detect_rack_handler
|
781
|
+
servers = Array(self.server)
|
782
|
+
servers.each do |server_name|
|
783
|
+
begin
|
784
|
+
return Rack::Handler.get(server_name)
|
785
|
+
rescue LoadError
|
786
|
+
rescue NameError
|
787
|
+
end
|
788
|
+
end
|
789
|
+
fail "Server handler (#{servers.join(',')}) not found."
|
790
|
+
end
|
791
|
+
|
792
|
+
def construct_middleware(builder=Rack::Builder.new)
|
793
|
+
builder.use Rack::Session::Cookie if sessions?
|
794
|
+
builder.use Rack::CommonLogger if logging?
|
795
|
+
builder.use Rack::MethodOverride if methodoverride?
|
796
|
+
@middleware.each { |c, args, bk| builder.use(c, *args, &bk) }
|
797
|
+
builder.run new
|
798
|
+
@callsite = builder.to_app
|
799
|
+
end
|
800
|
+
|
801
|
+
def reset_middleware
|
802
|
+
@callsite = nil
|
803
|
+
end
|
804
|
+
|
805
|
+
def reset!(subclass = self)
|
806
|
+
subclass.routes = dupe_routes
|
807
|
+
subclass.templates = templates.dup
|
808
|
+
subclass.conditions = []
|
809
|
+
subclass.filters = filters.dup
|
810
|
+
subclass.errors = errors.dup
|
811
|
+
subclass.middleware = middleware.dup
|
812
|
+
subclass.send :reset_middleware
|
813
|
+
end
|
814
|
+
|
815
|
+
def inherited(subclass)
|
816
|
+
reset!(subclass)
|
817
|
+
super
|
818
|
+
end
|
819
|
+
|
820
|
+
def reloading?
|
821
|
+
@reloading ||= false
|
822
|
+
end
|
823
|
+
|
824
|
+
@@mutex = Mutex.new
|
825
|
+
def synchronize(&block)
|
826
|
+
if lock?
|
827
|
+
@@mutex.synchronize(&block)
|
828
|
+
else
|
829
|
+
yield
|
830
|
+
end
|
831
|
+
end
|
832
|
+
|
833
|
+
def dupe_routes
|
834
|
+
routes.inject({}) do |hash,(request_method,routes)|
|
835
|
+
hash[request_method] = routes.dup
|
836
|
+
hash
|
837
|
+
end
|
838
|
+
end
|
839
|
+
|
840
|
+
def metadef(message, &block)
|
841
|
+
(class << self; self; end).
|
842
|
+
send :define_method, message, &block
|
843
|
+
end
|
844
|
+
end
|
845
|
+
|
846
|
+
set :raise_errors, true
|
847
|
+
set :dump_errors, false
|
848
|
+
set :clean_trace, true
|
849
|
+
set :sessions, false
|
850
|
+
set :logging, false
|
851
|
+
set :methodoverride, false
|
852
|
+
set :static, false
|
853
|
+
set :environment, (ENV['RACK_ENV'] || :development).to_sym
|
854
|
+
|
855
|
+
set :run, false
|
856
|
+
set :server, %w[thin mongrel webrick]
|
857
|
+
set :host, '0.0.0.0'
|
858
|
+
set :port, 4567
|
859
|
+
|
860
|
+
set :app_file, nil
|
861
|
+
set :root, Proc.new { app_file && File.expand_path(File.dirname(app_file)) }
|
862
|
+
set :views, Proc.new { root && File.join(root, 'views') }
|
863
|
+
set :public, Proc.new { root && File.join(root, 'public') }
|
864
|
+
set :reload, Proc.new { app_file? && app_file !~ /\.ru$/i && development? }
|
865
|
+
set :lock, Proc.new { reload? }
|
866
|
+
|
867
|
+
# static files route
|
868
|
+
get(/.*[^\/]$/) do
|
869
|
+
pass unless options.static? && options.public?
|
870
|
+
path = options.public + unescape(request.path_info)
|
871
|
+
pass unless File.file?(path)
|
872
|
+
send_file path, :disposition => nil
|
873
|
+
end
|
874
|
+
|
875
|
+
error ::Exception do
|
876
|
+
response.status = 500
|
877
|
+
content_type 'text/html'
|
878
|
+
'<h1>Internal Server Error</h1>'
|
879
|
+
end
|
880
|
+
|
881
|
+
configure :development do
|
882
|
+
get '/__sinatra__/:image.png' do
|
883
|
+
filename = File.dirname(__FILE__) + "/images/#{params[:image]}.png"
|
884
|
+
content_type :png
|
885
|
+
send_file filename
|
886
|
+
end
|
887
|
+
|
888
|
+
error NotFound do
|
889
|
+
(<<-HTML).gsub(/^ {8}/, '')
|
890
|
+
<!DOCTYPE html>
|
891
|
+
<html>
|
892
|
+
<head>
|
893
|
+
<style type="text/css">
|
894
|
+
body { text-align:center;font-family:helvetica,arial;font-size:22px;
|
895
|
+
color:#888;margin:20px}
|
896
|
+
#c {margin:0 auto;width:500px;text-align:left}
|
897
|
+
</style>
|
898
|
+
</head>
|
899
|
+
<body>
|
900
|
+
<h2>Sinatra doesn't know this ditty.</h2>
|
901
|
+
<img src='/__sinatra__/404.png'>
|
902
|
+
<div id="c">
|
903
|
+
Try this:
|
904
|
+
<pre>#{request.request_method.downcase} '#{request.path_info}' do\n "Hello World"\nend</pre>
|
905
|
+
</div>
|
906
|
+
</body>
|
907
|
+
</html>
|
908
|
+
HTML
|
909
|
+
end
|
910
|
+
|
911
|
+
error do
|
912
|
+
next unless err = request.env['sinatra.error']
|
913
|
+
heading = err.class.name + ' - ' + err.message.to_s
|
914
|
+
(<<-HTML).gsub(/^ {8}/, '')
|
915
|
+
<!DOCTYPE html>
|
916
|
+
<html>
|
917
|
+
<head>
|
918
|
+
<style type="text/css">
|
919
|
+
body {font-family:verdana;color:#333}
|
920
|
+
#c {margin-left:20px}
|
921
|
+
h1 {color:#1D6B8D;margin:0;margin-top:-30px}
|
922
|
+
h2 {color:#1D6B8D;font-size:18px}
|
923
|
+
pre {border-left:2px solid #ddd;padding-left:10px;color:#000}
|
924
|
+
img {margin-top:10px}
|
925
|
+
</style>
|
926
|
+
</head>
|
927
|
+
<body>
|
928
|
+
<div id="c">
|
929
|
+
<img src="/__sinatra__/500.png">
|
930
|
+
<h1>#{escape_html(heading)}</h1>
|
931
|
+
<pre>#{escape_html(clean_backtrace(err.backtrace) * "\n")}</pre>
|
932
|
+
<h2>Params</h2>
|
933
|
+
<pre>#{escape_html(params.inspect)}</pre>
|
934
|
+
</div>
|
935
|
+
</body>
|
936
|
+
</html>
|
937
|
+
HTML
|
938
|
+
end
|
939
|
+
end
|
940
|
+
end
|
941
|
+
|
942
|
+
# Base class for classic style (top-level) applications.
|
943
|
+
class Default < Base
|
944
|
+
set :raise_errors, Proc.new { test? }
|
945
|
+
set :dump_errors, true
|
946
|
+
set :sessions, false
|
947
|
+
set :logging, Proc.new { ! test? }
|
948
|
+
set :methodoverride, true
|
949
|
+
set :static, true
|
950
|
+
set :run, Proc.new { ! test? }
|
951
|
+
|
952
|
+
def self.register(*extensions, &block) #:nodoc:
|
953
|
+
added_methods = extensions.map {|m| m.public_instance_methods }.flatten
|
954
|
+
Delegator.delegate *added_methods
|
955
|
+
super(*extensions, &block)
|
956
|
+
end
|
957
|
+
end
|
958
|
+
|
959
|
+
# The top-level Application. All DSL methods executed on main are delegated
|
960
|
+
# to this class.
|
961
|
+
class Application < Default
|
962
|
+
end
|
963
|
+
|
964
|
+
module Delegator #:nodoc:
|
965
|
+
def self.delegate(*methods)
|
966
|
+
methods.each do |method_name|
|
967
|
+
eval <<-RUBY, binding, '(__DELEGATE__)', 1
|
968
|
+
def #{method_name}(*args, &b)
|
969
|
+
::Sinatra::Application.#{method_name}(*args, &b)
|
970
|
+
end
|
971
|
+
private :#{method_name}
|
972
|
+
RUBY
|
973
|
+
end
|
974
|
+
end
|
975
|
+
|
976
|
+
delegate :get, :put, :post, :delete, :head, :template, :layout, :before,
|
977
|
+
:error, :not_found, :configures, :configure, :set, :set_option,
|
978
|
+
:set_options, :enable, :disable, :use, :development?, :test?,
|
979
|
+
:production?, :use_in_file_templates!, :helpers
|
980
|
+
end
|
981
|
+
|
982
|
+
def self.new(base=Base, options={}, &block)
|
983
|
+
base = Class.new(base)
|
984
|
+
base.send :class_eval, &block if block_given?
|
985
|
+
base
|
986
|
+
end
|
987
|
+
|
988
|
+
# Extend the top-level DSL with the modules provided.
|
989
|
+
def self.register(*extensions, &block)
|
990
|
+
Default.register(*extensions, &block)
|
991
|
+
end
|
992
|
+
|
993
|
+
# Include the helper modules provided in Sinatra's request context.
|
994
|
+
def self.helpers(*extensions, &block)
|
995
|
+
Default.helpers(*extensions, &block)
|
996
|
+
end
|
997
|
+
end
|
998
|
+
|
999
|
+
class String #:nodoc:
|
1000
|
+
# Define String#each under 1.9 for Rack compatibility. This should be
|
1001
|
+
# removed once Rack is fully 1.9 compatible.
|
1002
|
+
alias_method :each, :each_line unless ''.respond_to? :each
|
1003
|
+
|
1004
|
+
# Define String#bytesize as an alias to String#length for Ruby 1.8.6 and
|
1005
|
+
# earlier.
|
1006
|
+
alias_method :bytesize, :length unless ''.respond_to? :bytesize
|
1007
|
+
end
|