sinarey 1.0.4 → 1.0.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d6654df4698a8572a480da5f376cc50cfa583e01
4
- data.tar.gz: 8fec9fe56d30cfc66cc77b6b5e1e4e70de9b2893
3
+ metadata.gz: 7d806350786262ea84053daf3d50fd560f8ed578
4
+ data.tar.gz: e1144b327ea25f94fe868ae852a805e5a51ad0ca
5
5
  SHA512:
6
- metadata.gz: 42e320d517bdc56a8303b87a67512ebbc2097c61e30a43e44eee3e4a645c59801dd237659e9f68f4875da865c67291d642ca0cdc74d445bd641cc22dbe5ae585
7
- data.tar.gz: 3a487e6e3bb06577fb1f7e0b8c37bed3bb8d9e5c0dbcdff0946d218df34bd97b86d344b66f6bcdfb53926994b0db9a34b8a299fdcaad950dde2c6d487410c026
6
+ metadata.gz: 303a3809add2b65e12454e5f37a0a4597cc923d14b009dac7f1ff73e71211f2ae434adc6166f53a1b5dcf2c6a1db958679c45f99e550ff0ee86226ef22c94ac5
7
+ data.tar.gz: cd18c2100e9ca2fc341bf1efb3d51e94fc8d328948721c74dc422b4ddb35040cd0e18352d34bf7285c29b9be026e9b4ab1e6fca27e10064c5dd96c62db5c471c
data/README.md CHANGED
@@ -6,6 +6,9 @@ Sinarey, base on Sinatra, use for large rack app project
6
6
 
7
7
  1.0.4: add pjax request support.
8
8
 
9
+ 1.0.5: remove logic about pjax;
10
+ add rack.framework, sinarey.params;
11
+ remove sinatra's original pass logic;
9
12
 
10
13
 
11
14
  yeah.happy coding:)
@@ -1,19 +1,30 @@
1
1
 
2
- #$LOAD_PATH.unshift File.expand_path('../lib',__dir__)
3
- #require 'sinarey/base'
2
+ require 'sinarey/base'
4
3
 
5
4
  class Application < Sinatra::SinareyBase
6
5
 
6
+ before do
7
+ puts "before at app1"
8
+ end
9
+
10
+ before "/app1/:id" do
11
+ puts "before app1 # #{params[:id]}"
12
+ end
13
+
14
+ after do
15
+ p env['sinarey.params']
16
+ end
17
+
7
18
  error do
8
19
  'error at app1'
9
20
  end
10
21
 
11
22
  get '/' do
23
+
12
24
  'index'
13
25
  end
14
26
 
15
27
  get '/app1' do
16
- p '123'
17
28
  'app1'
18
29
  end
19
30
 
@@ -23,7 +34,19 @@ class Application < Sinatra::SinareyBase
23
34
  end
24
35
 
25
36
  get '/app1/:id' do
37
+
26
38
  "app1 # #{params[:id]}"
27
39
  end
28
40
 
41
+ get '/app1/*.*' do
42
+
43
+ "app1 # #{params[splat]}"
44
+ end
45
+
46
+ get %r{^/tracks/([\d]+)/([\d]+)$} do |id, track_id|
47
+ params[:id] = id
48
+ params[:track_id] = track_id
49
+ "tracks #{params[:id]}"
50
+ end
51
+
29
52
  end
@@ -1,6 +1,5 @@
1
1
 
2
- #$LOAD_PATH.unshift File.expand_path('../lib',__dir__)
3
- #require 'sinarey/base'
2
+ require 'sinarey/base'
4
3
 
5
4
  class Application2 < Sinatra::SinareyBase
6
5
 
@@ -1,5 +1,5 @@
1
1
 
2
- require 'sinarey'
2
+ $LOAD_PATH.unshift File.expand_path('../lib',__dir__)
3
3
 
4
4
  require_relative 'app'
5
5
  require_relative 'app2'
@@ -11,4 +11,7 @@ appRouter = Sinarey::Router.new do
11
11
  mount Application2
12
12
  notfound NotfoundApp
13
13
  end
14
+
14
15
  run appRouter
16
+
17
+ #run Application
@@ -1,6 +1,5 @@
1
1
 
2
- #$LOAD_PATH.unshift File.expand_path('../lib',__dir__)
3
- #require 'sinarey/base'
2
+ require 'sinarey/base'
4
3
 
5
4
  class NotfoundApp < Sinatra::SinareyBase
6
5
 
@@ -1,1220 +1,1195 @@
1
- require 'sinatra/base'
2
-
3
- module Sinatra
4
-
5
- class SinareyBase
6
- include Rack::Utils
7
- include Helpers
8
- include Templates
9
-
10
- URI_INSTANCE = URI.const_defined?(:Parser) ? URI::Parser.new : URI
11
-
12
- attr_accessor :app, :env, :request, :response, :params
13
- attr_reader :template_cache
14
-
15
- def initialize(app = nil)
16
- super()
17
- @app = app
18
- @template_cache = Tilt::Cache.new
19
- yield self if block_given?
20
- end
21
-
22
- # Rack call interface.
23
- def call(env)
24
- dup.call!(env)
25
- end
26
-
27
- def call!(env) # :nodoc:
28
- @env = env
29
- @request = Request.new(env)
30
- @response = Response.new
31
- @params = indifferent_params(@request.params)
32
- template_cache.clear if settings.reload_templates
33
- force_encoding(@params)
34
-
35
- route = @request.path_info
36
- route.chop! if (char=route[-1]) and char=='/' # ignore last '/' char
37
-
38
- #pjax request support
39
- if route.chomp!('.pjax')
40
- env["IS_PJAX_REQUEST"] = true
41
- end
42
-
43
- @response['Content-Type'] = nil
44
- invoke { dispatch! }
45
- invoke { error_block!(response.status) } unless @env['sinatra.error']
46
-
47
- unless @response['Content-Type']
48
- if Array === body and body[0].respond_to? :content_type
49
- content_type body[0].content_type
50
- else
51
- content_type :html
52
- end
53
- end
54
-
55
- @response.finish
56
- end
57
-
58
- # Access settings defined with Base.set.
59
- def self.settings
60
- self
61
- end
62
-
63
- # Access settings defined with Base.set.
64
- def settings
65
- self.class.settings
66
- end
67
-
68
- def options
69
- warn "Sinatra::Base#options is deprecated and will be removed, " \
70
- "use #settings instead."
71
- settings
72
- end
73
-
74
- # Exit the current block, halts any further processing
75
- # of the request, and returns the specified response.
76
- def halt(*response)
77
- response = response.first if response.length == 1
78
- throw :halt, response
79
- end
80
-
81
- # Pass control to the next matching route.
82
- # If there are no more matching routes, Sinatra will
83
- # return a 404 response.
84
- def pass(&block)
85
- throw :pass, block
86
- end
87
-
88
- # Forward the request to the downstream app -- middleware only.
89
- def forward
90
- fail "downstream app not set" unless @app.respond_to? :call
91
- status, headers, body = @app.call env
92
- @response.status = status
93
- @response.body = body
94
- @response.headers.merge! headers
95
- nil
96
- end
97
-
98
- private
99
-
100
- # Run filters defined on the class and all superclasses.
101
- def filter!(type, base = settings)
102
- filter! type, base.superclass if base.superclass.respond_to?(:filters)
103
- base.filters[type].each { |args| process_filter(*args) }
104
- end
105
-
106
- # Run routes defined on the class and all superclasses.
107
- def route!(base = settings, pass_block = nil)
108
- if router = env['sinarey.router']
109
- return mount_route!(router)
110
- end
111
-
112
- if turbo_route = (turbo_routes = base.turbo_routes[@request.request_method]) && (path_info = turbo_routes[@request.path_info])
113
- turbo_route.tap do |block_id|
114
- block = base.blocks[block_id]
115
- returned_pass_block = process_turbo_route do |*args|
116
- env['sinatra.route'] = block.instance_variable_get(:@route_name)
117
- route_eval { block[*args] }
118
- end
119
- pass_block = returned_pass_block if returned_pass_block
120
- end
121
- elsif routes = base.routes[@request.request_method]
122
- routes.each do |pattern, keys, conditions, block_id|
123
- block = base.blocks[block_id]
124
- returned_pass_block = process_route(pattern, keys, conditions) do |*args|
125
- env['sinatra.route'] = block.instance_variable_get(:@route_name)
126
- route_eval { block[*args] }
127
- end
128
- # don't wipe out pass_block in superclass
129
- pass_block = returned_pass_block if returned_pass_block
130
- end
131
- end
132
-
133
- # Run routes defined in superclass.
134
- if base.superclass.respond_to?(:routes)
135
- return route!(base.superclass, pass_block)
136
- end
137
-
138
- route_eval(&pass_block) if pass_block
139
- route_missing
140
- end
141
-
142
- def mount_route!(options,base = settings)
143
- type,block_id = options[:type],options[:block_id]
144
- case type
145
- when :turbo
146
- block = base.blocks[block_id]
147
- returned_pass_block = process_turbo_route do |*args|
148
- env['sinatra.route'] = block.instance_variable_get(:@route_name)
149
- route_eval { block[*args] }
150
- end
151
- pass_block = returned_pass_block if returned_pass_block
152
- when :normal
153
- match,keys,conditions = options[:match],options[:keys],options[:conditions]
154
- block = base.blocks[block_id]
155
- returned_pass_block = process_mount_route(match, keys, conditions) do |*args|
156
- env['sinatra.route'] = block.instance_variable_get(:@route_name)
157
- route_eval { block[*args] }
158
- end
159
- # don't wipe out pass_block in superclass
160
- pass_block = returned_pass_block if returned_pass_block
161
- end
162
-
163
- route_eval(&pass_block) if pass_block
164
- route_missing
165
- end
166
-
167
- # Run a route block and throw :halt with the result.
168
- def route_eval
169
- throw :halt, yield
170
- end
171
-
172
- # If the current request matches pattern and conditions, fill params
173
- # with keys and call the given block.
174
- # Revert params afterwards.
175
- #
176
- # Returns pass block.
177
-
178
- def process_route(pattern, keys, conditions, block_id = nil, values = [])
179
- route = @request.path_info
180
- return unless match = pattern.match(route)
181
- values += match.captures.map! { |v| force_encoding URI_INSTANCE.unescape(v) if v }
182
-
183
- if values.any?
184
- original, @params = params, params.merge('splat' => [], 'captures' => values)
185
- keys.zip(values) { |k,v| Array === @params[k] ? @params[k] << v : @params[k] = v if v }
186
- end
187
-
188
- catch(:pass) do
189
- conditions.each { |c| throw :pass if c.bind(self).call == false }
190
- (block_id && (block = settings.blocks[block_id])) ? block[self, values] : yield(self, values)
191
- end
192
- ensure
193
- @params = original if original
194
- end
195
-
196
- def process_turbo_route(block = nil)
197
- catch(:pass) do
198
- block ? block[self, []] : yield(self, [])
199
- end
200
- end
201
-
202
- def process_mount_route(match, keys, conditions, block_id = nil, values = [])
203
- values += match.captures.map! { |v| force_encoding URI_INSTANCE.unescape(v) if v }
204
-
205
- if values.any?
206
- original, @params = params, params.merge('splat' => [], 'captures' => values)
207
- keys.zip(values) { |k,v| Array === @params[k] ? @params[k] << v : @params[k] = v if v }
208
- end
209
-
210
- catch(:pass) do
211
- conditions.each { |c| throw :pass if c.bind(self).call == false }
212
- (block_id && (block = settings.blocks[block_id])) ? block[self, values] : yield(self, values)
213
- end
214
- ensure
215
- @params = original if original
216
- end
217
-
218
- def process_filter(pattern, keys, conditions, block = nil, values = [])
219
- route = @request.path_info
220
- route = '/' if route.empty? and not settings.empty_path_info?
221
- return unless match = pattern.match(route)
222
- values += match.captures.map! { |v| force_encoding URI_INSTANCE.unescape(v) if v }
223
-
224
- if values.any?
225
- original, @params = params, params.merge('splat' => [], 'captures' => values)
226
- keys.zip(values) { |k,v| Array === @params[k] ? @params[k] << v : @params[k] = v if v }
227
- end
228
-
229
- catch(:pass) do
230
- conditions.each { |c| throw :pass if c.bind(self).call == false }
231
- block ? block[self, values] : yield(self, values)
232
- end
233
- ensure
234
- @params = original if original
235
- end
236
-
237
- # No matching route was found or all routes passed. The default
238
- # implementation is to forward the request downstream when running
239
- # as middleware (@app is non-nil); when no downstream app is set, raise
240
- # a NotFound exception. Subclasses can override this method to perform
241
- # custom route miss logic.
242
- def route_missing
243
- if @app
244
- forward
245
- else
246
- raise NotFound
247
- end
248
- end
249
-
250
- # Attempt to serve static files from public directory. Throws :halt when
251
- # a matching file is found, returns nil otherwise.
252
- def static!
253
- return if (public_dir = settings.public_folder).nil?
254
- path = File.expand_path("#{public_dir}#{unescape(request.path_info)}" )
255
- return unless File.file?(path)
256
-
257
- env['sinatra.static_file'] = path
258
- cache_control(*settings.static_cache_control) if settings.static_cache_control?
259
- send_file path, :disposition => nil
260
- end
261
-
262
- # Enable string or symbol key access to the nested params hash.
263
- def indifferent_params(object)
264
- case object
265
- when Hash
266
- new_hash = indifferent_hash
267
- object.each { |key, value| new_hash[key] = indifferent_params(value) }
268
- new_hash
269
- when Array
270
- object.map { |item| indifferent_params(item) }
271
- else
272
- object
273
- end
274
- end
275
-
276
- # Creates a Hash with indifferent access.
277
- def indifferent_hash
278
- Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
279
- end
280
-
281
- # Run the block with 'throw :halt' support and apply result to the response.
282
- def invoke
283
- res = catch(:halt) { yield }
284
- res = [res] if Fixnum === res or String === res
285
- if Array === res and Fixnum === res.first
286
- res = res.dup
287
- status(res.shift)
288
- body(res.pop)
289
- headers(*res)
290
- elsif res.respond_to? :each
291
- body res
292
- end
293
- nil # avoid double setting the same response tuple twice
294
- end
295
-
296
- # Dispatch a request with error handling.
297
- def dispatch!
298
- invoke do
299
- static! if settings.static? && (request.get? || request.head?)
300
- filter! :before
301
- route!
302
- end
303
- rescue ::Exception => boom
304
- invoke { handle_exception!(boom) }
305
- ensure
306
- begin
307
- filter! :after
308
- rescue ::Exception => boom
309
- invoke { handle_exception!(boom) } unless @env['sinatra.error']
310
- end
311
- end
312
-
313
- # Error handling during requests.
314
- def handle_exception!(boom)
315
- @env['sinatra.error'] = boom
316
-
317
- if boom.respond_to? :http_status
318
- status(boom.http_status)
319
- elsif settings.use_code? and boom.respond_to? :code and boom.code.between? 400, 599
320
- status(boom.code)
321
- else
322
- status(500)
323
- end
324
-
325
- status(500) unless status.between? 400, 599
326
-
327
- if server_error?
328
- dump_errors! boom if settings.dump_errors?
329
- raise boom if settings.show_exceptions? and settings.show_exceptions != :after_handler
330
- end
331
-
332
- if not_found?
333
- body '<h1>Not Found</h1>'
334
- end
335
-
336
- res = error_block!(boom.class, boom) || error_block!(status, boom)
337
- return res if res or not server_error?
338
- raise boom if settings.raise_errors? or settings.show_exceptions?
339
- error_block! Exception, boom
340
- end
341
-
342
- # Find an custom error block for the key(s) specified.
343
- def error_block!(key, *block_params)
344
- base = settings
345
- while base.respond_to?(:errors)
346
- next base = base.superclass unless args_array = base.errors[key]
347
- args_array.reverse_each do |args|
348
- first = args == args_array.first
349
- args += [block_params]
350
- resp = process_route(*args)
351
- return resp unless resp.nil? && !first
352
- end
353
- end
354
- return false unless key.respond_to? :superclass and key.superclass < Exception
355
- error_block!(key.superclass, *block_params)
356
- end
357
-
358
- def dump_errors!(boom)
359
- msg = ["#{boom.class} - #{boom.message}:", *boom.backtrace].join("\n\t")
360
- @env['rack.errors'].puts(msg)
361
- end
362
-
363
- class << self
364
- CALLERS_TO_IGNORE = [ # :nodoc:
365
- /\/sinatra(\/(base|main|showexceptions))?\.rb$/, # all sinatra code
366
- /lib\/tilt.*\.rb$/, # all tilt code
367
- /^\(.*\)$/, # generated code
368
- /rubygems\/(custom|core_ext\/kernel)_require\.rb$/, # rubygems require hacks
369
- /active_support/, # active_support require hacks
370
- /bundler(\/runtime)?\.rb/, # bundler require hacks
371
- /<internal:/, # internal in ruby >= 1.9.2
372
- /src\/kernel\/bootstrap\/[A-Z]/ # maglev kernel files
373
- ]
374
-
375
- # contrary to what the comment said previously, rubinius never supported this
376
- if defined?(RUBY_IGNORE_CALLERS)
377
- warn "RUBY_IGNORE_CALLERS is deprecated and will no longer be supported by Sinatra 2.0"
378
- CALLERS_TO_IGNORE.concat(RUBY_IGNORE_CALLERS)
379
- end
380
-
381
- attr_reader :blocks, :turbo_routes, :routes, :filters, :templates, :errors
382
-
383
- # Removes all routes, filters, middleware and extension hooks from the
384
- # current class (not routes/filters/... defined by its superclass).
385
- def reset!
386
- @conditions = []
387
- @routes = {}
388
- @turbo_routes = {}
389
- @blocks = {}
390
- @filters = {:before => [], :after => []}
391
- @errors = {}
392
- @middleware = []
393
- @prototype = nil
394
- @extensions = []
395
-
396
- if superclass.respond_to?(:templates)
397
- @templates = Hash.new { |hash,key| superclass.templates[key] }
398
- else
399
- @templates = {}
400
- end
401
- end
402
-
403
- # Extension modules registered on this class and all superclasses.
404
- def extensions
405
- if superclass.respond_to?(:extensions)
406
- (@extensions + superclass.extensions).uniq
407
- else
408
- @extensions
409
- end
410
- end
411
-
412
- # Middleware used in this class and all superclasses.
413
- def middleware
414
- if superclass.respond_to?(:middleware)
415
- superclass.middleware + @middleware
416
- else
417
- @middleware
418
- end
419
- end
420
-
421
- # Sets an option to the given value. If the value is a proc,
422
- # the proc will be called every time the option is accessed.
423
- def set(option, value = (not_set = true), ignore_setter = false, &block)
424
- raise ArgumentError if block and !not_set
425
- value, not_set = block, false if block
426
-
427
- if not_set
428
- raise ArgumentError unless option.respond_to?(:each)
429
- option.each { |k,v| set(k, v) }
430
- return self
431
- end
432
-
433
- if respond_to?("#{option}=") and not ignore_setter
434
- return __send__("#{option}=", value)
435
- end
436
-
437
- setter = proc { |val| set option, val, true }
438
- getter = proc { value }
439
-
440
- case value
441
- when Proc
442
- getter = value
443
- when Symbol, Fixnum, FalseClass, TrueClass, NilClass
444
- getter = value.inspect
445
- when Hash
446
- setter = proc do |val|
447
- val = value.merge val if Hash === val
448
- set option, val, true
449
- end
450
- end
451
-
452
- define_singleton("#{option}=", setter) if setter
453
- define_singleton(option, getter) if getter
454
- define_singleton("#{option}?", "!!#{option}") unless method_defined? "#{option}?"
455
- self
456
- end
457
-
458
- # Same as calling `set :option, true` for each of the given options.
459
- def enable(*opts)
460
- opts.each { |key| set(key, true) }
461
- end
462
-
463
- # Same as calling `set :option, false` for each of the given options.
464
- def disable(*opts)
465
- opts.each { |key| set(key, false) }
466
- end
467
-
468
- # Define a custom error handler. Optionally takes either an Exception
469
- # class, or an HTTP status code to specify which errors should be
470
- # handled.
471
- def error(*codes, &block)
472
- args = compile! "ERROR", //, block
473
- codes = codes.map { |c| Array(c) }.flatten
474
- codes << Exception if codes.empty?
475
- codes.each { |c| (@errors[c] ||= []) << args }
476
- end
477
-
478
- # Sugar for `error(404) { ... }`
479
- def not_found(&block)
480
- error(404, &block)
481
- error(Sinatra::NotFound, &block)
482
- end
483
-
484
- # Define a named template. The block must return the template source.
485
- def template(name, &block)
486
- filename, line = caller_locations.first
487
- templates[name] = [block, filename, line.to_i]
488
- end
489
-
490
- # Define the layout template. The block must return the template source.
491
- def layout(name = :layout, &block)
492
- template name, &block
493
- end
494
-
495
- # Load embedded templates from the file; uses the caller's __FILE__
496
- # when no file is specified.
497
- def inline_templates=(file = nil)
498
- file = (file.nil? || file == true) ? (caller_files.first || File.expand_path($0)) : file
499
-
500
- begin
501
- io = ::IO.respond_to?(:binread) ? ::IO.binread(file) : ::IO.read(file)
502
- app, data = io.gsub("\r\n", "\n").split(/^__END__$/, 2)
503
- rescue Errno::ENOENT
504
- app, data = nil
505
- end
506
-
507
- if data
508
- if app and app =~ /([^\n]*\n)?#[^\n]*coding: *(\S+)/m
509
- encoding = $2
510
- else
511
- encoding = settings.default_encoding
512
- end
513
- lines = app.count("\n") + 1
514
- template = nil
515
- force_encoding data, encoding
516
- data.each_line do |line|
517
- lines += 1
518
- if line =~ /^@@\s*(.*\S)\s*$/
519
- template = force_encoding('', encoding)
520
- templates[$1.to_sym] = [template, file, lines]
521
- elsif template
522
- template << line
523
- end
524
- end
525
- end
526
- end
527
-
528
- # Lookup or register a mime type in Rack's mime registry.
529
- def mime_type(type, value = nil)
530
- return type if type.nil?
531
- return type.to_s if type.to_s.include?('/')
532
- type = ".#{type}" unless type.to_s[0] == ?.
533
- return Rack::Mime.mime_type(type, nil) unless value
534
- Rack::Mime::MIME_TYPES[type] = value
535
- end
536
-
537
- # provides all mime types matching type, including deprecated types:
538
- # mime_types :html # => ['text/html']
539
- # mime_types :js # => ['application/javascript', 'text/javascript']
540
- def mime_types(type)
541
- type = mime_type type
542
- type =~ /^application\/(xml|javascript)$/ ? [type, "text/#$1"] : [type]
543
- end
544
-
545
- # Define a before filter; runs before all requests within the same
546
- # context as route handlers and may access/modify the request and
547
- # response.
548
- def before(path = nil, options = {}, &block)
549
- add_filter(:before, path, options, &block)
550
- end
551
-
552
- # Define an after filter; runs after all requests within the same
553
- # context as route handlers and may access/modify the request and
554
- # response.
555
- def after(path = nil, options = {}, &block)
556
- add_filter(:after, path, options, &block)
557
- end
558
-
559
- # add a filter
560
- def add_filter(type, path = nil, options = {}, &block)
561
- path, options = //, path if path.respond_to?(:each_pair)
562
- filters[type] << compile_filter!(type, path || //, block, options)
563
- end
564
-
565
- # Add a route condition. The route is considered non-matching when the
566
- # block returns false.
567
- def condition(name = "#{caller.first[/`.*'/]} condition", &block)
568
- @conditions << generate_method(name, &block)
569
- end
570
-
571
- def public=(value)
572
- warn ":public is no longer used to avoid overloading Module#public, use :public_folder or :public_dir instead"
573
- set(:public_folder, value)
574
- end
575
-
576
- def public_dir=(value)
577
- self.public_folder = value
578
- end
579
-
580
- def public_dir
581
- public_folder
582
- end
583
-
584
- # Defining a `GET` handler also automatically defines
585
- # a `HEAD` handler.
586
- def get(path, opts = {}, &block)
587
- conditions = @conditions.dup
588
- route('GET', path, opts, &block)
589
-
590
- @conditions = conditions
591
- route('HEAD', path, opts, &block)
592
- end
593
-
594
- def put(path, opts = {}, &bk) route 'PUT', path, opts, &bk end
595
- def post(path, opts = {}, &bk) route 'POST', path, opts, &bk end
596
- def delete(path, opts = {}, &bk) route 'DELETE', path, opts, &bk end
597
- def head(path, opts = {}, &bk) route 'HEAD', path, opts, &bk end
598
- def options(path, opts = {}, &bk) route 'OPTIONS', path, opts, &bk end
599
- def patch(path, opts = {}, &bk) route 'PATCH', path, opts, &bk end
600
- def link(path, opts = {}, &bk) route 'LINK', path, opts, &bk end
601
- def unlink(path, opts = {}, &bk) route 'UNLINK', path, opts, &bk end
602
-
603
- # Makes the methods defined in the block and in the Modules given
604
- # in `extensions` available to the handlers and templates
605
- def helpers(*extensions, &block)
606
- class_eval(&block) if block_given?
607
- include(*extensions) if extensions.any?
608
- end
609
-
610
- # Register an extension. Alternatively take a block from which an
611
- # extension will be created and registered on the fly.
612
- def register(*extensions, &block)
613
- extensions << Module.new(&block) if block_given?
614
- @extensions += extensions
615
- extensions.each do |extension|
616
- extend extension
617
- extension.registered(self) if extension.respond_to?(:registered)
618
- end
619
- end
620
-
621
- def development?; environment == :development end
622
- def production?; environment == :production end
623
- def test?; environment == :test end
624
-
625
- # Set configuration options for Sinatra and/or the app.
626
- # Allows scoping of settings for certain environments.
627
- def configure(*envs)
628
- yield self if envs.empty? || envs.include?(environment.to_sym)
629
- end
630
-
631
- # Use the specified Rack middleware
632
- def use(middleware, *args, &block)
633
- @prototype = nil
634
- @middleware << [middleware, args, block]
635
- end
636
-
637
- # Stop the self-hosted server if running.
638
- def quit!
639
- return unless running?
640
- # Use Thin's hard #stop! if available, otherwise just #stop.
641
- running_server.respond_to?(:stop!) ? running_server.stop! : running_server.stop
642
- $stderr.puts "== Sinatra has ended his set (crowd applauds)" unless handler_name =~/cgi/i
643
- set :running_server, nil
644
- set :handler_name, nil
645
- end
646
-
647
- alias_method :stop!, :quit!
648
-
649
- # Run the Sinatra app as a self-hosted server using
650
- # Thin, Puma, Mongrel, or WEBrick (in that order). If given a block, will call
651
- # with the constructed handler once we have taken the stage.
652
- def run!(options = {}, &block)
653
- return if running?
654
- set options
655
- handler = detect_rack_handler
656
- handler_name = handler.name.gsub(/.*::/, '')
657
- server_settings = settings.respond_to?(:server_settings) ? settings.server_settings : {}
658
- server_settings.merge!(:Port => port, :Host => bind)
659
-
660
- begin
661
- start_server(handler, server_settings, handler_name, &block)
662
- rescue Errno::EADDRINUSE
663
- $stderr.puts "== Someone is already performing on port #{port}!"
664
- raise
665
- ensure
666
- quit!
667
- end
668
- end
669
-
670
- alias_method :start!, :run!
671
-
672
- # Check whether the self-hosted server is running or not.
673
- def running?
674
- running_server?
675
- end
676
-
677
- # The prototype instance used to process requests.
678
- def prototype
679
- @prototype ||= new
680
- end
681
-
682
- # Create a new instance without middleware in front of it.
683
- alias new! new unless method_defined? :new!
684
-
685
- # Create a new instance of the class fronted by its middleware
686
- # pipeline. The object is guaranteed to respond to #call but may not be
687
- # an instance of the class new was called on.
688
- def new(*args, &bk)
689
- instance = new!(*args, &bk)
690
- Wrapper.new(build(instance).to_app, instance)
691
- end
692
-
693
- # Creates a Rack::Builder instance with all the middleware set up and
694
- # the given +app+ as end point.
695
- def build(app)
696
- builder = Rack::Builder.new
697
- setup_default_middleware builder
698
- setup_middleware builder
699
- builder.run app
700
- builder
701
- end
702
-
703
- def call(env)
704
- synchronize { prototype.call(env) }
705
- end
706
-
707
- # Like Kernel#caller but excluding certain magic entries and without
708
- # line / method information; the resulting array contains filenames only.
709
- def caller_files
710
- cleaned_caller(1).flatten
711
- end
712
-
713
- # Like caller_files, but containing Arrays rather than strings with the
714
- # first element being the file, and the second being the line.
715
- def caller_locations
716
- cleaned_caller 2
717
- end
718
-
719
- private
720
-
721
- # Starts the server by running the Rack Handler.
722
- def start_server(handler, server_settings, handler_name)
723
- handler.run(self, server_settings) do |server|
724
- unless handler_name =~ /cgi/i
725
- $stderr.puts "== Sinatra/#{Sinatra::VERSION} has taken the stage " +
726
- "on #{port} for #{environment} with backup from #{handler_name}"
727
- end
728
-
729
- setup_traps
730
- set :running_server, server
731
- set :handler_name, handler_name
732
- server.threaded = settings.threaded if server.respond_to? :threaded=
733
-
734
- yield server if block_given?
735
- end
736
- end
737
-
738
- def setup_traps
739
- if traps?
740
- at_exit { quit! }
741
-
742
- [:INT, :TERM].each do |signal|
743
- old_handler = trap(signal) do
744
- quit!
745
- old_handler.call if old_handler.respond_to?(:call)
746
- end
747
- end
748
-
749
- set :traps, false
750
- end
751
- end
752
-
753
- # Dynamically defines a method on settings.
754
- def define_singleton(name, content = Proc.new)
755
- # replace with call to singleton_class once we're 1.9 only
756
- (class << self; self; end).class_eval do
757
- undef_method(name) if method_defined? name
758
- String === content ? class_eval("def #{name}() #{content}; end") : define_method(name, &content)
759
- end
760
- end
761
-
762
- # Condition for matching host name. Parameter might be String or Regexp.
763
- def host_name(pattern)
764
- condition { pattern === request.host }
765
- end
766
-
767
- # Condition for matching user agent. Parameter should be Regexp.
768
- # Will set params[:agent].
769
- def user_agent(pattern)
770
- condition do
771
- if request.user_agent.to_s =~ pattern
772
- @params[:agent] = $~[1..-1]
773
- true
774
- else
775
- false
776
- end
777
- end
778
- end
779
- alias_method :agent, :user_agent
780
-
781
- # Condition for matching mimetypes. Accepts file extensions.
782
- def provides(*types)
783
- types.map! { |t| mime_types(t) }
784
- types.flatten!
785
- condition do
786
- if type = response['Content-Type']
787
- types.include? type or types.include? type[/^[^;]+/]
788
- elsif type = request.preferred_type(types)
789
- params = (type.respond_to?(:params) ? type.params : {})
790
- content_type(type, params)
791
- true
792
- else
793
- false
794
- end
795
- end
796
- end
797
-
798
- def route(verb, path, options = {}, &block)
799
- if path.class == String
800
- return if path.empty?
801
- path.chop! if (char=path[-1]) and char=='/'
802
- end
803
-
804
- # Because of self.options.host
805
- host_name(options.delete(:host)) if options.key?(:host)
806
-
807
- if /^[a-zA-Z0-9\-\/_]*$/ === path
808
- turbo_signature = turbo_compile!(verb, path, block, options)
809
- @turbo_routes[verb] ||= {}
810
- @turbo_routes[verb][path] = turbo_signature
811
- else
812
- signature = compile!(verb, path, block, options)
813
- (@routes[verb] ||= []) << signature
814
- end
815
- invoke_hook(:route_added, verb, path, block)
816
- signature
817
- end
818
-
819
- def invoke_hook(name, *args)
820
- extensions.each { |e| e.send(name, *args) if e.respond_to?(name) }
821
- end
822
-
823
- def generate_method(method_name, &block)
824
- define_method(method_name, &block)
825
- method = instance_method method_name
826
- remove_method method_name
827
- method
828
- end
829
-
830
- def turbo_compile!(verb, path, block, options = {})
831
- method_name = "#{verb} #{path}"
832
- unbound_method = generate_method(method_name, &block)
833
- wrapper = block.arity != 0 ?
834
- proc { |a,p| unbound_method.bind(a).call(*p) } :
835
- proc { |a,p| unbound_method.bind(a).call }
836
- wrapper.instance_variable_set(:@route_name, method_name)
837
- @blocks ||= {}
838
- block_id = @blocks.size + 1
839
- @blocks[block_id] = wrapper
840
- block_id
841
- end
842
-
843
- def compile!(verb, path, block, options = {})
844
- options.each_pair { |option, args| send(option, *args) }
845
- method_name = "#{verb} #{path}"
846
- unbound_method = generate_method(method_name, &block)
847
- pattern, keys = compile path
848
- conditions, @conditions = @conditions, []
849
-
850
- wrapper = block.arity != 0 ?
851
- proc { |a,p| unbound_method.bind(a).call(*p) } :
852
- proc { |a,p| unbound_method.bind(a).call }
853
- wrapper.instance_variable_set(:@route_name, method_name)
854
- @blocks ||= {}
855
- block_id = @blocks.size + 1
856
- @blocks[block_id] = wrapper
857
- [ pattern, keys, conditions, block_id ]
858
- end
859
-
860
- def compile_filter!(verb, path, block, options = {})
861
- options.each_pair { |option, args| send(option, *args) }
862
- method_name = "#{verb} #{path}"
863
- unbound_method = generate_method(method_name, &block)
864
- pattern, keys = compile path
865
- conditions, @conditions = @conditions, []
866
-
867
- wrapper = block.arity != 0 ?
868
- proc { |a,p| unbound_method.bind(a).call(*p) } :
869
- proc { |a,p| unbound_method.bind(a).call }
870
- wrapper.instance_variable_set(:@route_name, method_name)
871
-
872
- [ pattern, keys, conditions, wrapper ]
873
- end
874
-
875
-
876
- def compile(path)
877
- if path.respond_to? :to_str
878
- keys = []
879
-
880
- # We append a / at the end if there was one.
881
- # Reason: Splitting does not split off an empty
882
- # string at the end if the split separator
883
- # is at the end.
884
- #
885
- postfix = '/' if path =~ /\/\z/
886
-
887
- # Split the path into pieces in between forward slashes.
888
- #
889
- segments = path.split('/').map! do |segment|
890
- ignore = []
891
-
892
- # Special character handling.
893
- #
894
- pattern = segment.to_str.gsub(/[^\?\%\\\/\:\*\w]/) do |c|
895
- ignore << escaped(c).join if c.match(/[\.@]/)
896
- patt = encoded(c)
897
- patt.gsub(/%[\da-fA-F]{2}/) do |match|
898
- match.split(//).map! {|char| char =~ /[A-Z]/ ? "[#{char}#{char.tr('A-Z', 'a-z')}]" : char}.join
899
- end
900
- end
901
-
902
- ignore = ignore.uniq.join
903
-
904
- # Key handling.
905
- #
906
- pattern.gsub(/((:\w+)|\*)/) do |match|
907
- if match == "*"
908
- keys << 'splat'
909
- "(.*?)"
910
- else
911
- keys << $2[1..-1]
912
- ignore_pattern = safe_ignore(ignore)
913
-
914
- ignore_pattern
915
- end
916
- end
917
- end
918
-
919
- # Special case handling.
920
- #
921
- if segment = segments.pop
922
- if segment.match(/\[\^\\\./)
923
- parts = segment.rpartition(/\[\^\\\./)
924
- parts[1] = '[^'
925
- segments << parts.join
926
- else
927
- segments << segment
928
- end
929
- end
930
- [/\A#{segments.join('/')}#{postfix}\z/, keys]
931
- elsif path.respond_to?(:keys) && path.respond_to?(:match)
932
- [path, path.keys]
933
- elsif path.respond_to?(:names) && path.respond_to?(:match)
934
- [path, path.names]
935
- elsif path.respond_to? :match
936
- [path, []]
937
- else
938
- raise TypeError, path
939
- end
940
- end
941
-
942
- def encoded(char)
943
- enc = URI_INSTANCE.escape(char)
944
- enc = "(?:#{escaped(char, enc).join('|')})" if enc == char
945
- enc = "(?:#{enc}|#{encoded('+')})" if char == " "
946
- enc
947
- end
948
-
949
- def escaped(char, enc = URI_INSTANCE.escape(char))
950
- [Regexp.escape(enc), URI_INSTANCE.escape(char, /./)]
951
- end
952
-
953
- def safe_ignore(ignore)
954
- unsafe_ignore = []
955
- ignore = ignore.gsub(/%[\da-fA-F]{2}/) do |hex|
956
- unsafe_ignore << hex[1..2]
957
- ''
958
- end
959
- unsafe_patterns = unsafe_ignore.map! do |unsafe|
960
- chars = unsafe.split(//).map! do |char|
961
- if char =~ /[A-Z]/
962
- char <<= char.tr('A-Z', 'a-z')
963
- end
964
- char
965
- end
966
-
967
- "|(?:%[^#{chars[0]}].|%[#{chars[0]}][^#{chars[1]}])"
968
- end
969
- if unsafe_patterns.length > 0
970
- "((?:[^#{ignore}/?#%]#{unsafe_patterns.join()})+)"
971
- else
972
- "([^#{ignore}/?#]+)"
973
- end
974
- end
975
-
976
- def setup_default_middleware(builder)
977
- builder.use ExtendedRack
978
- builder.use ShowExceptions if show_exceptions?
979
- builder.use Rack::MethodOverride if method_override?
980
- builder.use Rack::Head
981
- setup_logging builder
982
- setup_sessions builder
983
- setup_protection builder
984
- end
985
-
986
- def setup_middleware(builder)
987
- middleware.each { |c,a,b| builder.use(c, *a, &b) }
988
- end
989
-
990
- def setup_logging(builder)
991
- if logging?
992
- setup_common_logger(builder)
993
- setup_custom_logger(builder)
994
- elsif logging == false
995
- setup_null_logger(builder)
996
- end
997
- end
998
-
999
- def setup_null_logger(builder)
1000
- builder.use Rack::NullLogger
1001
- end
1002
-
1003
- def setup_common_logger(builder)
1004
- builder.use Sinatra::CommonLogger
1005
- end
1006
-
1007
- def setup_custom_logger(builder)
1008
- if logging.respond_to? :to_int
1009
- builder.use Rack::Logger, logging
1010
- else
1011
- builder.use Rack::Logger
1012
- end
1013
- end
1014
-
1015
- def setup_protection(builder)
1016
- return unless protection?
1017
- options = Hash === protection ? protection.dup : {}
1018
- protect_session = options.fetch(:session) { sessions? }
1019
- options[:except] = Array options[:except]
1020
- options[:except] += [:session_hijacking, :remote_token] unless protect_session
1021
- options[:reaction] ||= :drop_session
1022
- builder.use Rack::Protection, options
1023
- end
1024
-
1025
- def setup_sessions(builder)
1026
- return unless sessions?
1027
- options = {}
1028
- options[:secret] = session_secret if session_secret?
1029
- options.merge! sessions.to_hash if sessions.respond_to? :to_hash
1030
- builder.use Rack::Session::Cookie, options
1031
- end
1032
-
1033
- def detect_rack_handler
1034
- servers = Array(server)
1035
- servers.each do |server_name|
1036
- begin
1037
- return Rack::Handler.get(server_name.to_s)
1038
- rescue LoadError, NameError
1039
- end
1040
- end
1041
- fail "Server handler (#{servers.join(',')}) not found."
1042
- end
1043
-
1044
- def inherited(subclass)
1045
- subclass.reset!
1046
- subclass.set :app_file, caller_files.first unless subclass.app_file?
1047
- super
1048
- end
1049
-
1050
- @@mutex = Mutex.new
1051
- def synchronize(&block)
1052
- if lock?
1053
- @@mutex.synchronize(&block)
1054
- else
1055
- yield
1056
- end
1057
- end
1058
-
1059
- # used for deprecation warnings
1060
- def warn(message)
1061
- super message + "\n\tfrom #{cleaned_caller.first.join(':')}"
1062
- end
1063
-
1064
- # Like Kernel#caller but excluding certain magic entries
1065
- def cleaned_caller(keep = 3)
1066
- caller(1).
1067
- map! { |line| line.split(/:(?=\d|in )/, 3)[0,keep] }.
1068
- reject { |file, *_| CALLERS_TO_IGNORE.any? { |pattern| file =~ pattern } }
1069
- end
1070
- end
1071
-
1072
- # Fixes encoding issues by
1073
- # * defaulting to UTF-8
1074
- # * casting params to Encoding.default_external
1075
- #
1076
- # The latter might not be necessary if Rack handles it one day.
1077
- # Keep an eye on Rack's LH #100.
1078
- def force_encoding(*args) settings.force_encoding(*args) end
1079
- if defined? Encoding
1080
- def self.force_encoding(data, encoding = default_encoding)
1081
- return if data == settings || data.is_a?(Tempfile)
1082
- if data.respond_to? :force_encoding
1083
- data.force_encoding(encoding).encode!
1084
- elsif data.respond_to? :each_value
1085
- data.each_value { |v| force_encoding(v, encoding) }
1086
- elsif data.respond_to? :each
1087
- data.each { |v| force_encoding(v, encoding) }
1088
- end
1089
- data
1090
- end
1091
- else
1092
- def self.force_encoding(data, *) data end
1093
- end
1094
-
1095
- reset!
1096
-
1097
- set :environment, (ENV['RACK_ENV'] || :development).to_sym
1098
- set :raise_errors, Proc.new { test? }
1099
- set :dump_errors, Proc.new { !test? }
1100
- set :show_exceptions, Proc.new { development? }
1101
- set :sessions, false
1102
- set :logging, false
1103
- set :protection, true
1104
- set :method_override, false
1105
- set :use_code, false
1106
- set :default_encoding, "utf-8"
1107
- set :x_cascade, true
1108
- set :add_charset, %w[javascript xml xhtml+xml json].map { |t| "application/#{t}" }
1109
- settings.add_charset << /^text\//
1110
-
1111
- # explicitly generating a session secret eagerly to play nice with preforking
1112
- begin
1113
- require 'securerandom'
1114
- set :session_secret, SecureRandom.hex(64)
1115
- rescue LoadError, NotImplementedError
1116
- # SecureRandom raises a NotImplementedError if no random device is available
1117
- set :session_secret, "%064x" % Kernel.rand(2**256-1)
1118
- end
1119
-
1120
- class << self
1121
- alias_method :methodoverride?, :method_override?
1122
- alias_method :methodoverride=, :method_override=
1123
- end
1124
-
1125
- set :run, false # start server via at-exit hook?
1126
- set :running_server, nil
1127
- set :handler_name, nil
1128
- set :traps, true
1129
- set :server, %w[HTTP webrick]
1130
- set :bind, Proc.new { development? ? 'localhost' : '0.0.0.0' }
1131
- set :port, Integer(ENV['PORT'] && !ENV['PORT'].empty? ? ENV['PORT'] : 4567)
1132
-
1133
- ruby_engine = defined?(RUBY_ENGINE) && RUBY_ENGINE
1134
-
1135
- if ruby_engine == 'macruby'
1136
- server.unshift 'control_tower'
1137
- else
1138
- server.unshift 'reel'
1139
- server.unshift 'mongrel' if ruby_engine.nil?
1140
- server.unshift 'puma' if ruby_engine != 'rbx'
1141
- server.unshift 'thin' if ruby_engine != 'jruby'
1142
- server.unshift 'puma' if ruby_engine == 'rbx'
1143
- server.unshift 'trinidad' if ruby_engine == 'jruby'
1144
- end
1145
-
1146
- set :absolute_redirects, true
1147
- set :prefixed_redirects, false
1148
- set :empty_path_info, nil
1149
-
1150
- set :app_file, nil
1151
- set :root, Proc.new { app_file && File.expand_path(File.dirname(app_file)) }
1152
- set :views, Proc.new { root && File.join(root, 'views') }
1153
- set :reload_templates, Proc.new { development? }
1154
- set :lock, false
1155
- set :threaded, true
1156
-
1157
- set :public_folder, Proc.new { root && File.join(root, 'public') }
1158
- set :static, Proc.new { public_folder && File.exist?(public_folder) }
1159
- set :static_cache_control, false
1160
-
1161
- error ::Exception do
1162
- response.status = 500
1163
- content_type 'text/html'
1164
- '<h1>Internal Server Error</h1>'
1165
- end
1166
-
1167
- configure :development do
1168
- get '/__sinatra__/:image.png' do
1169
- filename = File.dirname(__FILE__) + "/images/#{params[:image]}.png"
1170
- content_type :png
1171
- send_file filename
1172
- end
1173
-
1174
- error NotFound do
1175
- content_type 'text/html'
1176
-
1177
- if self.class == Sinatra::Application
1178
- code = <<-RUBY.gsub(/^ {12}/, '')
1179
- #{request.request_method.downcase} '#{request.path_info}' do
1180
- "Hello World"
1181
- end
1182
- RUBY
1183
- else
1184
- code = <<-RUBY.gsub(/^ {12}/, '')
1185
- class #{self.class}
1186
- #{request.request_method.downcase} '#{request.path_info}' do
1187
- "Hello World"
1188
- end
1189
- end
1190
- RUBY
1191
-
1192
- file = settings.app_file.to_s.sub(settings.root.to_s, '').sub(/^\//, '')
1193
- code = "# in #{file}\n#{code}" unless file.empty?
1194
- end
1195
-
1196
- (<<-HTML).gsub(/^ {10}/, '')
1197
- <!DOCTYPE html>
1198
- <html>
1199
- <head>
1200
- <style type="text/css">
1201
- body { text-align:center;font-family:helvetica,arial;font-size:22px;
1202
- color:#888;margin:20px}
1203
- #c {margin:0 auto;width:500px;text-align:left}
1204
- </style>
1205
- </head>
1206
- <body>
1207
- <h2>Sinatra doesn&rsquo;t know this ditty.</h2>
1208
- <img src='#{uri "/__sinatra__/404.png"}'>
1209
- <div id="c">
1210
- Try this:
1211
- <pre>#{code}</pre>
1212
- </div>
1213
- </body>
1214
- </html>
1215
- HTML
1216
- end
1217
- end
1218
- end
1219
-
1
+ require 'sinatra/base'
2
+
3
+ module Sinatra
4
+
5
+ class SinareyBase
6
+ include Rack::Utils
7
+ include Helpers
8
+ include Templates
9
+
10
+ URI_INSTANCE = URI.const_defined?(:Parser) ? URI::Parser.new : URI
11
+
12
+ attr_accessor :app, :env, :request, :response, :params
13
+ attr_reader :template_cache
14
+
15
+ def initialize(app = nil)
16
+ super()
17
+ @app = app
18
+ @template_cache = Tilt::Cache.new
19
+ yield self if block_given?
20
+ end
21
+
22
+ # Rack call interface.
23
+ def call(env)
24
+ env['rack.framework'] = "Sinarey"
25
+ dup.call!(env)
26
+ end
27
+
28
+ def call!(env) # :nodoc:
29
+ @env = env
30
+ @request = Request.new(env)
31
+ @response = Response.new
32
+ @params = indifferent_params(@request.params)
33
+ template_cache.clear if settings.reload_templates
34
+ force_encoding(@params)
35
+
36
+ route = @request.path_info
37
+ route.chop! if (char=route[-1]) and char=='/' # ignore last '/' char
38
+
39
+ @response['Content-Type'] = nil
40
+ invoke { dispatch! }
41
+ invoke { error_block!(response.status) } unless @env['sinatra.error']
42
+
43
+ unless @response['Content-Type']
44
+ if Array === body and body[0].respond_to? :content_type
45
+ content_type body[0].content_type
46
+ else
47
+ content_type :html
48
+ end
49
+ end
50
+
51
+ @response.finish
52
+ end
53
+
54
+ # Access settings defined with Base.set.
55
+ def self.settings
56
+ self
57
+ end
58
+
59
+ # Access settings defined with Base.set.
60
+ def settings
61
+ self.class.settings
62
+ end
63
+
64
+ def options
65
+ warn "Sinatra::Base#options is deprecated and will be removed, " \
66
+ "use #settings instead."
67
+ settings
68
+ end
69
+
70
+ # Exit the current block, halts any further processing
71
+ # of the request, and returns the specified response.
72
+ def halt(*response)
73
+ response = response.first if response.length == 1
74
+ throw :halt, response
75
+ end
76
+
77
+ # Forward the request to the downstream app -- middleware only.
78
+ def forward
79
+ fail "downstream app not set" unless @app.respond_to? :call
80
+ status, headers, body = @app.call env
81
+ @response.status = status
82
+ @response.body = body
83
+ @response.headers.merge! headers
84
+ nil
85
+ end
86
+
87
+ private
88
+
89
+ # Run filters defined on the class and all superclasses.
90
+ def filter!(type, base = settings)
91
+ filter! type, base.superclass if base.superclass.respond_to?(:filters)
92
+ base.filters[type].each { |args| process_filter(*args) }
93
+ end
94
+
95
+ # Run routes defined on the class and all superclasses.
96
+ def route!(base = settings)
97
+ if router = env['sinarey.router']
98
+ return mount_route!(router)
99
+ end
100
+
101
+ if turbo_route = (turbo_routes = base.turbo_routes[@request.request_method]) && (path_info = turbo_routes[@request.path_info])
102
+ turbo_route.tap do |block_id|
103
+ process_turbo_route do |*args|
104
+ block = base.blocks[block_id]
105
+ action_block_wrap(block, *args)
106
+ end
107
+ end
108
+ elsif routes = base.routes[@request.request_method]
109
+ routes.each do |pattern, keys, conditions, block_id|
110
+ process_route(pattern, keys, conditions) do |*args|
111
+ block = base.blocks[block_id]
112
+ action_block_wrap(block, *args)
113
+ end
114
+ end
115
+ end
116
+
117
+ # Run routes defined in superclass.
118
+ if base.superclass.respond_to?(:routes)
119
+ return route!(base.superclass)
120
+ end
121
+
122
+ route_missing
123
+ end
124
+
125
+ def mount_route!(options,base = settings)
126
+ type,block_id = options[:type],options[:block_id]
127
+ case type
128
+ when :turbo
129
+ process_turbo_route do |*args|
130
+ block = base.blocks[block_id]
131
+ action_block_wrap(block, *args)
132
+ end
133
+ when :normal
134
+ match,keys,conditions = options[:match],options[:keys],options[:conditions]
135
+ process_mount_route(match, keys, conditions) do |*args|
136
+ block = base.blocks[block_id]
137
+ action_block_wrap(block, *args)
138
+ end
139
+ end
140
+
141
+ route_missing
142
+ end
143
+
144
+ def action_block_wrap(block, *args)
145
+ env['sinatra.route'] = block.instance_variable_get(:@route_name)
146
+ route_eval { block[*args] }
147
+ ensure
148
+ env['sinarey.params'] = @params
149
+ end
150
+
151
+ # Run a route block and throw :halt with the result.
152
+ def route_eval
153
+ throw :halt, yield
154
+ end
155
+
156
+ def process_mount_route(match, keys, conditions, block_id = nil, values = [], &callback)
157
+ values += match.captures.map! { |v| force_encoding URI_INSTANCE.unescape(v) if v }
158
+
159
+ if values.any?
160
+ original, @params = params, params.merge('splat' => [], 'captures' => values)
161
+ keys.zip(values) do |k,v|
162
+ if Array === @params[k]
163
+ @params[k] << v
164
+ elsif v
165
+ @params[k] = v
166
+ end
167
+ end
168
+ end
169
+
170
+ (block_id && (block = settings.blocks[block_id])) ? block[self, values] : yield(self, values)
171
+ ensure
172
+ @params = original if original
173
+ end
174
+
175
+ # If the current request matches pattern and conditions, fill params
176
+ # with keys and call the given block.
177
+ # Revert params afterwards.
178
+ #
179
+ # Returns pass block.
180
+
181
+ def process_route(pattern, keys, conditions, block_id = nil, values = [], &callback)
182
+ route = @request.path_info
183
+ return unless match = pattern.match(route)
184
+
185
+ process_mount_route(match, keys, conditions, block_id, values, &callback)
186
+ end
187
+
188
+ def process_turbo_route(block = nil)
189
+ block ? block[self, []] : yield(self, [])
190
+ end
191
+
192
+ def process_filter(pattern, keys, conditions, block = nil, values = [])
193
+ route = @request.path_info
194
+ route = '/' if route.empty? and not settings.empty_path_info?
195
+ return unless match = pattern.match(route)
196
+ values += match.captures.map! { |v| force_encoding URI_INSTANCE.unescape(v) if v }
197
+
198
+ if values.any?
199
+ original, @params = params, params.merge('splat' => [], 'captures' => values)
200
+ keys.zip(values) { |k,v| Array === @params[k] ? @params[k] << v : @params[k] = v if v }
201
+ end
202
+
203
+ block ? block[self, values] : yield(self, values)
204
+ ensure
205
+ @params = original if original
206
+ end
207
+
208
+ # No matching route was found. The default
209
+ # implementation is to forward the request downstream when running
210
+ # as middleware (@app is non-nil); when no downstream app is set, raise
211
+ # a NotFound exception. Subclasses can override this method to perform
212
+ # custom route miss logic.
213
+ def route_missing
214
+ if @app
215
+ forward
216
+ else
217
+ raise NotFound
218
+ end
219
+ end
220
+
221
+ # Attempt to serve static files from public directory. Throws :halt when
222
+ # a matching file is found, returns nil otherwise.
223
+ def static!
224
+ return if (public_dir = settings.public_folder).nil?
225
+ path = File.expand_path("#{public_dir}#{unescape(request.path_info)}" )
226
+ return unless File.file?(path)
227
+
228
+ env['sinatra.static_file'] = path
229
+ cache_control(*settings.static_cache_control) if settings.static_cache_control?
230
+ send_file path, :disposition => nil
231
+ end
232
+
233
+ # Enable string or symbol key access to the nested params hash.
234
+ def indifferent_params(object)
235
+ case object
236
+ when Hash
237
+ new_hash = indifferent_hash
238
+ object.each { |key, value| new_hash[key] = indifferent_params(value) }
239
+ new_hash
240
+ when Array
241
+ object.map { |item| indifferent_params(item) }
242
+ else
243
+ object
244
+ end
245
+ end
246
+
247
+ # Creates a Hash with indifferent access.
248
+ def indifferent_hash
249
+ Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
250
+ end
251
+
252
+ # Run the block with 'throw :halt' support and apply result to the response.
253
+ def invoke
254
+ res = catch(:halt) { yield }
255
+ res = [res] if Fixnum === res or String === res
256
+ if Array === res and Fixnum === res.first
257
+ res = res.dup
258
+ status(res.shift)
259
+ body(res.pop)
260
+ headers(*res)
261
+ elsif res.respond_to? :each
262
+ body res
263
+ end
264
+ nil # avoid double setting the same response tuple twice
265
+ end
266
+
267
+ # Dispatch a request with error handling.
268
+ def dispatch!
269
+ invoke do
270
+ static! if settings.static? && (request.get? || request.head?)
271
+ filter! :before
272
+ route!
273
+ end
274
+ rescue ::Exception => boom
275
+ invoke { handle_exception!(boom) }
276
+ ensure
277
+ begin
278
+ filter! :after
279
+ rescue ::Exception => boom
280
+ invoke { handle_exception!(boom) } unless @env['sinatra.error']
281
+ end
282
+ end
283
+
284
+ # Error handling during requests.
285
+ def handle_exception!(boom)
286
+ @env['sinatra.error'] = boom
287
+
288
+ if boom.respond_to? :http_status
289
+ status(boom.http_status)
290
+ elsif settings.use_code? and boom.respond_to? :code and boom.code.between? 400, 599
291
+ status(boom.code)
292
+ else
293
+ status(500)
294
+ end
295
+
296
+ status(500) unless status.between? 400, 599
297
+
298
+ if server_error?
299
+ dump_errors! boom if settings.dump_errors?
300
+ raise boom if settings.show_exceptions? and settings.show_exceptions != :after_handler
301
+ end
302
+
303
+ if not_found?
304
+ body '<h1>Not Found</h1>'
305
+ end
306
+
307
+ res = error_block!(boom.class, boom) || error_block!(status, boom)
308
+ return res if res or not server_error?
309
+ raise boom if settings.raise_errors? or settings.show_exceptions?
310
+ error_block! Exception, boom
311
+ end
312
+
313
+ # Find an custom error block for the key(s) specified.
314
+ def error_block!(key, *block_params)
315
+ base = settings
316
+ while base.respond_to?(:errors)
317
+ next base = base.superclass unless args_array = base.errors[key]
318
+ args_array.reverse_each do |args|
319
+ first = args == args_array.first
320
+ args += [block_params]
321
+ resp = process_route(*args)
322
+ return resp unless resp.nil? && !first
323
+ end
324
+ end
325
+ return false unless key.respond_to? :superclass and key.superclass < Exception
326
+ error_block!(key.superclass, *block_params)
327
+ end
328
+
329
+ def dump_errors!(boom)
330
+ msg = ["#{boom.class} - #{boom.message}:", *boom.backtrace].join("\n\t")
331
+ @env['rack.errors'].puts(msg)
332
+ end
333
+
334
+ class << self
335
+ CALLERS_TO_IGNORE = [ # :nodoc:
336
+ /\/sinatra(\/(base|main|showexceptions))?\.rb$/, # all sinatra code
337
+ /lib\/tilt.*\.rb$/, # all tilt code
338
+ /^\(.*\)$/, # generated code
339
+ /rubygems\/(custom|core_ext\/kernel)_require\.rb$/, # rubygems require hacks
340
+ /active_support/, # active_support require hacks
341
+ /bundler(\/runtime)?\.rb/, # bundler require hacks
342
+ /<internal:/, # internal in ruby >= 1.9.2
343
+ /src\/kernel\/bootstrap\/[A-Z]/ # maglev kernel files
344
+ ]
345
+
346
+ # contrary to what the comment said previously, rubinius never supported this
347
+ if defined?(RUBY_IGNORE_CALLERS)
348
+ warn "RUBY_IGNORE_CALLERS is deprecated and will no longer be supported by Sinatra 2.0"
349
+ CALLERS_TO_IGNORE.concat(RUBY_IGNORE_CALLERS)
350
+ end
351
+
352
+ attr_reader :blocks, :turbo_routes, :routes, :filters, :templates, :errors
353
+
354
+ # Removes all routes, filters, middleware and extension hooks from the
355
+ # current class (not routes/filters/... defined by its superclass).
356
+ def reset!
357
+ @conditions = []
358
+ @routes = {}
359
+ @turbo_routes = {}
360
+ @blocks = {}
361
+ @filters = {:before => [], :after => []}
362
+ @errors = {}
363
+ @middleware = []
364
+ @prototype = nil
365
+ @extensions = []
366
+
367
+ if superclass.respond_to?(:templates)
368
+ @templates = Hash.new { |hash,key| superclass.templates[key] }
369
+ else
370
+ @templates = {}
371
+ end
372
+ end
373
+
374
+ # Extension modules registered on this class and all superclasses.
375
+ def extensions
376
+ if superclass.respond_to?(:extensions)
377
+ (@extensions + superclass.extensions).uniq
378
+ else
379
+ @extensions
380
+ end
381
+ end
382
+
383
+ # Middleware used in this class and all superclasses.
384
+ def middleware
385
+ if superclass.respond_to?(:middleware)
386
+ superclass.middleware + @middleware
387
+ else
388
+ @middleware
389
+ end
390
+ end
391
+
392
+ # Sets an option to the given value. If the value is a proc,
393
+ # the proc will be called every time the option is accessed.
394
+ def set(option, value = (not_set = true), ignore_setter = false, &block)
395
+ raise ArgumentError if block and !not_set
396
+ value, not_set = block, false if block
397
+
398
+ if not_set
399
+ raise ArgumentError unless option.respond_to?(:each)
400
+ option.each { |k,v| set(k, v) }
401
+ return self
402
+ end
403
+
404
+ if respond_to?("#{option}=") and not ignore_setter
405
+ return __send__("#{option}=", value)
406
+ end
407
+
408
+ setter = proc { |val| set option, val, true }
409
+ getter = proc { value }
410
+
411
+ case value
412
+ when Proc
413
+ getter = value
414
+ when Symbol, Fixnum, FalseClass, TrueClass, NilClass
415
+ getter = value.inspect
416
+ when Hash
417
+ setter = proc do |val|
418
+ val = value.merge val if Hash === val
419
+ set option, val, true
420
+ end
421
+ end
422
+
423
+ define_singleton("#{option}=", setter) if setter
424
+ define_singleton(option, getter) if getter
425
+ define_singleton("#{option}?", "!!#{option}") unless method_defined? "#{option}?"
426
+ self
427
+ end
428
+
429
+ # Same as calling `set :option, true` for each of the given options.
430
+ def enable(*opts)
431
+ opts.each { |key| set(key, true) }
432
+ end
433
+
434
+ # Same as calling `set :option, false` for each of the given options.
435
+ def disable(*opts)
436
+ opts.each { |key| set(key, false) }
437
+ end
438
+
439
+ # Define a custom error handler. Optionally takes either an Exception
440
+ # class, or an HTTP status code to specify which errors should be
441
+ # handled.
442
+ def error(*codes, &block)
443
+ args = compile! "ERROR", //, block
444
+ codes = codes.map { |c| Array(c) }.flatten
445
+ codes << Exception if codes.empty?
446
+ codes.each { |c| (@errors[c] ||= []) << args }
447
+ end
448
+
449
+ # Sugar for `error(404) { ... }`
450
+ def not_found(&block)
451
+ error(404, &block)
452
+ error(Sinatra::NotFound, &block)
453
+ end
454
+
455
+ # Define a named template. The block must return the template source.
456
+ def template(name, &block)
457
+ filename, line = caller_locations.first
458
+ templates[name] = [block, filename, line.to_i]
459
+ end
460
+
461
+ # Define the layout template. The block must return the template source.
462
+ def layout(name = :layout, &block)
463
+ template name, &block
464
+ end
465
+
466
+ # Load embedded templates from the file; uses the caller's __FILE__
467
+ # when no file is specified.
468
+ def inline_templates=(file = nil)
469
+ file = (file.nil? || file == true) ? (caller_files.first || File.expand_path($0)) : file
470
+
471
+ begin
472
+ io = ::IO.respond_to?(:binread) ? ::IO.binread(file) : ::IO.read(file)
473
+ app, data = io.gsub("\r\n", "\n").split(/^__END__$/, 2)
474
+ rescue Errno::ENOENT
475
+ app, data = nil
476
+ end
477
+
478
+ if data
479
+ if app and app =~ /([^\n]*\n)?#[^\n]*coding: *(\S+)/m
480
+ encoding = $2
481
+ else
482
+ encoding = settings.default_encoding
483
+ end
484
+ lines = app.count("\n") + 1
485
+ template = nil
486
+ force_encoding data, encoding
487
+ data.each_line do |line|
488
+ lines += 1
489
+ if line =~ /^@@\s*(.*\S)\s*$/
490
+ template = force_encoding('', encoding)
491
+ templates[$1.to_sym] = [template, file, lines]
492
+ elsif template
493
+ template << line
494
+ end
495
+ end
496
+ end
497
+ end
498
+
499
+ # Lookup or register a mime type in Rack's mime registry.
500
+ def mime_type(type, value = nil)
501
+ return type if type.nil?
502
+ return type.to_s if type.to_s.include?('/')
503
+ type = ".#{type}" unless type.to_s[0] == ?.
504
+ return Rack::Mime.mime_type(type, nil) unless value
505
+ Rack::Mime::MIME_TYPES[type] = value
506
+ end
507
+
508
+ # provides all mime types matching type, including deprecated types:
509
+ # mime_types :html # => ['text/html']
510
+ # mime_types :js # => ['application/javascript', 'text/javascript']
511
+ def mime_types(type)
512
+ type = mime_type type
513
+ type =~ /^application\/(xml|javascript)$/ ? [type, "text/#$1"] : [type]
514
+ end
515
+
516
+ # Define a before filter; runs before all requests within the same
517
+ # context as route handlers and may access/modify the request and
518
+ # response.
519
+ def before(path = nil, options = {}, &block)
520
+ add_filter(:before, path, options, &block)
521
+ end
522
+
523
+ # Define an after filter; runs after all requests within the same
524
+ # context as route handlers and may access/modify the request and
525
+ # response.
526
+ def after(path = nil, options = {}, &block)
527
+ add_filter(:after, path, options, &block)
528
+ end
529
+
530
+ # add a filter
531
+ def add_filter(type, path = nil, options = {}, &block)
532
+ path, options = //, path if path.respond_to?(:each_pair)
533
+ filters[type] << compile_filter!(type, path || //, block, options)
534
+ end
535
+
536
+ # Add a route condition. The route is considered non-matching when the
537
+ # block returns false.
538
+ def condition(name = "#{caller.first[/`.*'/]} condition", &block)
539
+ @conditions << generate_method(name, &block)
540
+ end
541
+
542
+ def public=(value)
543
+ warn ":public is no longer used to avoid overloading Module#public, use :public_folder or :public_dir instead"
544
+ set(:public_folder, value)
545
+ end
546
+
547
+ def public_dir=(value)
548
+ self.public_folder = value
549
+ end
550
+
551
+ def public_dir
552
+ public_folder
553
+ end
554
+
555
+ # Defining a `GET` handler also automatically defines
556
+ # a `HEAD` handler.
557
+ def get(path, opts = {}, &block)
558
+ conditions = @conditions.dup
559
+ route('GET', path, opts, &block)
560
+
561
+ @conditions = conditions
562
+ route('HEAD', path, opts, &block)
563
+ end
564
+
565
+ def put(path, opts = {}, &bk) route 'PUT', path, opts, &bk end
566
+ def post(path, opts = {}, &bk) route 'POST', path, opts, &bk end
567
+ def delete(path, opts = {}, &bk) route 'DELETE', path, opts, &bk end
568
+ def head(path, opts = {}, &bk) route 'HEAD', path, opts, &bk end
569
+ def options(path, opts = {}, &bk) route 'OPTIONS', path, opts, &bk end
570
+ def patch(path, opts = {}, &bk) route 'PATCH', path, opts, &bk end
571
+ def link(path, opts = {}, &bk) route 'LINK', path, opts, &bk end
572
+ def unlink(path, opts = {}, &bk) route 'UNLINK', path, opts, &bk end
573
+
574
+ # Makes the methods defined in the block and in the Modules given
575
+ # in `extensions` available to the handlers and templates
576
+ def helpers(*extensions, &block)
577
+ class_eval(&block) if block_given?
578
+ include(*extensions) if extensions.any?
579
+ end
580
+
581
+ # Register an extension. Alternatively take a block from which an
582
+ # extension will be created and registered on the fly.
583
+ def register(*extensions, &block)
584
+ extensions << Module.new(&block) if block_given?
585
+ @extensions += extensions
586
+ extensions.each do |extension|
587
+ extend extension
588
+ extension.registered(self) if extension.respond_to?(:registered)
589
+ end
590
+ end
591
+
592
+ def development?; environment == :development end
593
+ def production?; environment == :production end
594
+ def test?; environment == :test end
595
+
596
+ # Set configuration options for Sinatra and/or the app.
597
+ # Allows scoping of settings for certain environments.
598
+ def configure(*envs)
599
+ yield self if envs.empty? || envs.include?(environment.to_sym)
600
+ end
601
+
602
+ # Use the specified Rack middleware
603
+ def use(middleware, *args, &block)
604
+ @prototype = nil
605
+ @middleware << [middleware, args, block]
606
+ end
607
+
608
+ # Stop the self-hosted server if running.
609
+ def quit!
610
+ return unless running?
611
+ # Use Thin's hard #stop! if available, otherwise just #stop.
612
+ running_server.respond_to?(:stop!) ? running_server.stop! : running_server.stop
613
+ $stderr.puts "== Sinatra has ended his set (crowd applauds)" unless handler_name =~/cgi/i
614
+ set :running_server, nil
615
+ set :handler_name, nil
616
+ end
617
+
618
+ alias_method :stop!, :quit!
619
+
620
+ # Run the Sinatra app as a self-hosted server using
621
+ # Thin, Puma, Mongrel, or WEBrick (in that order). If given a block, will call
622
+ # with the constructed handler once we have taken the stage.
623
+ def run!(options = {}, &block)
624
+ return if running?
625
+ set options
626
+ handler = detect_rack_handler
627
+ handler_name = handler.name.gsub(/.*::/, '')
628
+ server_settings = settings.respond_to?(:server_settings) ? settings.server_settings : {}
629
+ server_settings.merge!(:Port => port, :Host => bind)
630
+
631
+ begin
632
+ start_server(handler, server_settings, handler_name, &block)
633
+ rescue Errno::EADDRINUSE
634
+ $stderr.puts "== Someone is already performing on port #{port}!"
635
+ raise
636
+ ensure
637
+ quit!
638
+ end
639
+ end
640
+
641
+ alias_method :start!, :run!
642
+
643
+ # Check whether the self-hosted server is running or not.
644
+ def running?
645
+ running_server?
646
+ end
647
+
648
+ # The prototype instance used to process requests.
649
+ def prototype
650
+ @prototype ||= new
651
+ end
652
+
653
+ # Create a new instance without middleware in front of it.
654
+ alias new! new unless method_defined? :new!
655
+
656
+ # Create a new instance of the class fronted by its middleware
657
+ # pipeline. The object is guaranteed to respond to #call but may not be
658
+ # an instance of the class new was called on.
659
+ def new(*args, &bk)
660
+ instance = new!(*args, &bk)
661
+ Wrapper.new(build(instance).to_app, instance)
662
+ end
663
+
664
+ # Creates a Rack::Builder instance with all the middleware set up and
665
+ # the given +app+ as end point.
666
+ def build(app)
667
+ builder = Rack::Builder.new
668
+ setup_default_middleware builder
669
+ setup_middleware builder
670
+ builder.run app
671
+ builder
672
+ end
673
+
674
+ def call(env)
675
+ synchronize { prototype.call(env) }
676
+ end
677
+
678
+ # Like Kernel#caller but excluding certain magic entries and without
679
+ # line / method information; the resulting array contains filenames only.
680
+ def caller_files
681
+ cleaned_caller(1).flatten
682
+ end
683
+
684
+ # Like caller_files, but containing Arrays rather than strings with the
685
+ # first element being the file, and the second being the line.
686
+ def caller_locations
687
+ cleaned_caller 2
688
+ end
689
+
690
+ private
691
+
692
+ # Starts the server by running the Rack Handler.
693
+ def start_server(handler, server_settings, handler_name)
694
+ handler.run(self, server_settings) do |server|
695
+ unless handler_name =~ /cgi/i
696
+ $stderr.puts "== Sinatra/#{Sinatra::VERSION} has taken the stage " +
697
+ "on #{port} for #{environment} with backup from #{handler_name}"
698
+ end
699
+
700
+ setup_traps
701
+ set :running_server, server
702
+ set :handler_name, handler_name
703
+ server.threaded = settings.threaded if server.respond_to? :threaded=
704
+
705
+ yield server if block_given?
706
+ end
707
+ end
708
+
709
+ def setup_traps
710
+ if traps?
711
+ at_exit { quit! }
712
+
713
+ [:INT, :TERM].each do |signal|
714
+ old_handler = trap(signal) do
715
+ quit!
716
+ old_handler.call if old_handler.respond_to?(:call)
717
+ end
718
+ end
719
+
720
+ set :traps, false
721
+ end
722
+ end
723
+
724
+ # Dynamically defines a method on settings.
725
+ def define_singleton(name, content = Proc.new)
726
+ # replace with call to singleton_class once we're 1.9 only
727
+ (class << self; self; end).class_eval do
728
+ undef_method(name) if method_defined? name
729
+ String === content ? class_eval("def #{name}() #{content}; end") : define_method(name, &content)
730
+ end
731
+ end
732
+
733
+ # Condition for matching host name. Parameter might be String or Regexp.
734
+ def host_name(pattern)
735
+ condition { pattern === request.host }
736
+ end
737
+
738
+ # Condition for matching user agent. Parameter should be Regexp.
739
+ # Will set params[:agent].
740
+ def user_agent(pattern)
741
+ condition do
742
+ if request.user_agent.to_s =~ pattern
743
+ @params[:agent] = $~[1..-1]
744
+ true
745
+ else
746
+ false
747
+ end
748
+ end
749
+ end
750
+ alias_method :agent, :user_agent
751
+
752
+ # Condition for matching mimetypes. Accepts file extensions.
753
+ def provides(*types)
754
+ types.map! { |t| mime_types(t) }
755
+ types.flatten!
756
+ condition do
757
+ if type = response['Content-Type']
758
+ types.include? type or types.include? type[/^[^;]+/]
759
+ elsif type = request.preferred_type(types)
760
+ params = (type.respond_to?(:params) ? type.params : {})
761
+ content_type(type, params)
762
+ true
763
+ else
764
+ false
765
+ end
766
+ end
767
+ end
768
+
769
+ def route(verb, path, options = {}, &block)
770
+ if path.class == String
771
+ return if path.empty?
772
+ path.chop! if (char=path[-1]) and char=='/'
773
+ end
774
+
775
+ # Because of self.options.host
776
+ host_name(options.delete(:host)) if options.key?(:host)
777
+
778
+ if /^[a-zA-Z0-9\-\/_]*$/ === path
779
+ turbo_signature = turbo_compile!(verb, path, block, options)
780
+ @turbo_routes[verb] ||= {}
781
+ @turbo_routes[verb][path] = turbo_signature
782
+ else
783
+ signature = compile!(verb, path, block, options)
784
+ (@routes[verb] ||= []) << signature
785
+ end
786
+ invoke_hook(:route_added, verb, path, block)
787
+ signature
788
+ end
789
+
790
+ def invoke_hook(name, *args)
791
+ extensions.each { |e| e.send(name, *args) if e.respond_to?(name) }
792
+ end
793
+
794
+ def generate_method(method_name, &block)
795
+ define_method(method_name, &block)
796
+ method = instance_method method_name
797
+ remove_method method_name
798
+ method
799
+ end
800
+
801
+ def turbo_compile!(verb, path, block, options = {})
802
+ method_name = "#{verb} #{path}"
803
+ unbound_method = generate_method(method_name, &block)
804
+ wrapper = block.arity != 0 ?
805
+ proc { |a,p| unbound_method.bind(a).call(*p) } :
806
+ proc { |a,p| unbound_method.bind(a).call }
807
+ wrapper.instance_variable_set(:@route_name, method_name)
808
+ @blocks ||= {}
809
+ block_id = @blocks.size + 1
810
+ @blocks[block_id] = wrapper
811
+ block_id
812
+ end
813
+
814
+ def compile!(verb, path, block, options = {})
815
+ options.each_pair { |option, args| send(option, *args) }
816
+ method_name = "#{verb} #{path}"
817
+ unbound_method = generate_method(method_name, &block)
818
+ pattern, keys = compile path
819
+ conditions, @conditions = @conditions, []
820
+
821
+ wrapper = block.arity != 0 ?
822
+ proc { |a,p| unbound_method.bind(a).call(*p) } :
823
+ proc { |a,p| unbound_method.bind(a).call }
824
+ wrapper.instance_variable_set(:@route_name, method_name)
825
+ @blocks ||= {}
826
+ block_id = @blocks.size + 1
827
+ @blocks[block_id] = wrapper
828
+ [ pattern, keys, conditions, block_id ]
829
+ end
830
+
831
+ def compile_filter!(verb, path, block, options = {})
832
+ options.each_pair { |option, args| send(option, *args) }
833
+ method_name = "#{verb} #{path}"
834
+ unbound_method = generate_method(method_name, &block)
835
+ pattern, keys = compile path
836
+ conditions, @conditions = @conditions, []
837
+
838
+ wrapper = block.arity != 0 ?
839
+ proc { |a,p| unbound_method.bind(a).call(*p) } :
840
+ proc { |a,p| unbound_method.bind(a).call }
841
+ wrapper.instance_variable_set(:@route_name, method_name)
842
+
843
+ [ pattern, keys, conditions, wrapper ]
844
+ end
845
+
846
+
847
+ def compile(path)
848
+ if path.respond_to? :to_str
849
+ keys = []
850
+
851
+ # We append a / at the end if there was one.
852
+ # Reason: Splitting does not split off an empty
853
+ # string at the end if the split separator
854
+ # is at the end.
855
+ #
856
+ postfix = '/' if path =~ /\/\z/
857
+
858
+ # Split the path into pieces in between forward slashes.
859
+ #
860
+ segments = path.split('/').map! do |segment|
861
+ ignore = []
862
+
863
+ # Special character handling.
864
+ #
865
+ pattern = segment.to_str.gsub(/[^\?\%\\\/\:\*\w]/) do |c|
866
+ ignore << escaped(c).join if c.match(/[\.@]/)
867
+ patt = encoded(c)
868
+ patt.gsub(/%[\da-fA-F]{2}/) do |match|
869
+ match.split(//).map! {|char| char =~ /[A-Z]/ ? "[#{char}#{char.tr('A-Z', 'a-z')}]" : char}.join
870
+ end
871
+ end
872
+
873
+ ignore = ignore.uniq.join
874
+
875
+ # Key handling.
876
+ #
877
+ pattern.gsub(/((:\w+)|\*)/) do |match|
878
+ if match == "*"
879
+ keys << 'splat'
880
+ "(.*?)"
881
+ else
882
+ keys << $2[1..-1]
883
+ ignore_pattern = safe_ignore(ignore)
884
+
885
+ ignore_pattern
886
+ end
887
+ end
888
+ end
889
+
890
+ # Special case handling.
891
+ #
892
+ if segment = segments.pop
893
+ if segment.match(/\[\^\\\./)
894
+ parts = segment.rpartition(/\[\^\\\./)
895
+ parts[1] = '[^'
896
+ segments << parts.join
897
+ else
898
+ segments << segment
899
+ end
900
+ end
901
+ [/\A#{segments.join('/')}#{postfix}\z/, keys]
902
+ elsif path.respond_to?(:keys) && path.respond_to?(:match)
903
+ [path, path.keys]
904
+ elsif path.respond_to?(:names) && path.respond_to?(:match)
905
+ [path, path.names]
906
+ elsif path.respond_to? :match
907
+ [path, []]
908
+ else
909
+ raise TypeError, path
910
+ end
911
+ end
912
+
913
+ def encoded(char)
914
+ enc = URI_INSTANCE.escape(char)
915
+ enc = "(?:#{escaped(char, enc).join('|')})" if enc == char
916
+ enc = "(?:#{enc}|#{encoded('+')})" if char == " "
917
+ enc
918
+ end
919
+
920
+ def escaped(char, enc = URI_INSTANCE.escape(char))
921
+ [Regexp.escape(enc), URI_INSTANCE.escape(char, /./)]
922
+ end
923
+
924
+ def safe_ignore(ignore)
925
+ unsafe_ignore = []
926
+ ignore = ignore.gsub(/%[\da-fA-F]{2}/) do |hex|
927
+ unsafe_ignore << hex[1..2]
928
+ ''
929
+ end
930
+ unsafe_patterns = unsafe_ignore.map! do |unsafe|
931
+ chars = unsafe.split(//).map! do |char|
932
+ if char =~ /[A-Z]/
933
+ char <<= char.tr('A-Z', 'a-z')
934
+ end
935
+ char
936
+ end
937
+
938
+ "|(?:%[^#{chars[0]}].|%[#{chars[0]}][^#{chars[1]}])"
939
+ end
940
+ if unsafe_patterns.length > 0
941
+ "((?:[^#{ignore}/?#%]#{unsafe_patterns.join()})+)"
942
+ else
943
+ "([^#{ignore}/?#]+)"
944
+ end
945
+ end
946
+
947
+ def setup_default_middleware(builder)
948
+ builder.use ExtendedRack
949
+ builder.use ShowExceptions if show_exceptions?
950
+ builder.use Rack::MethodOverride if method_override?
951
+ builder.use Rack::Head
952
+ setup_logging builder
953
+ setup_sessions builder
954
+ setup_protection builder
955
+ end
956
+
957
+ def setup_middleware(builder)
958
+ middleware.each { |c,a,b| builder.use(c, *a, &b) }
959
+ end
960
+
961
+ def setup_logging(builder)
962
+ if logging?
963
+ setup_common_logger(builder)
964
+ setup_custom_logger(builder)
965
+ elsif logging == false
966
+ setup_null_logger(builder)
967
+ end
968
+ end
969
+
970
+ def setup_null_logger(builder)
971
+ builder.use Rack::NullLogger
972
+ end
973
+
974
+ def setup_common_logger(builder)
975
+ builder.use Sinatra::CommonLogger
976
+ end
977
+
978
+ def setup_custom_logger(builder)
979
+ if logging.respond_to? :to_int
980
+ builder.use Rack::Logger, logging
981
+ else
982
+ builder.use Rack::Logger
983
+ end
984
+ end
985
+
986
+ def setup_protection(builder)
987
+ return unless protection?
988
+ options = Hash === protection ? protection.dup : {}
989
+ protect_session = options.fetch(:session) { sessions? }
990
+ options[:except] = Array options[:except]
991
+ options[:except] += [:session_hijacking, :remote_token] unless protect_session
992
+ options[:reaction] ||= :drop_session
993
+ builder.use Rack::Protection, options
994
+ end
995
+
996
+ def setup_sessions(builder)
997
+ return unless sessions?
998
+ options = {}
999
+ options[:secret] = session_secret if session_secret?
1000
+ options.merge! sessions.to_hash if sessions.respond_to? :to_hash
1001
+ builder.use Rack::Session::Cookie, options
1002
+ end
1003
+
1004
+ def detect_rack_handler
1005
+ servers = Array(server)
1006
+ servers.each do |server_name|
1007
+ begin
1008
+ return Rack::Handler.get(server_name.to_s)
1009
+ rescue LoadError, NameError
1010
+ end
1011
+ end
1012
+ fail "Server handler (#{servers.join(',')}) not found."
1013
+ end
1014
+
1015
+ def inherited(subclass)
1016
+ subclass.reset!
1017
+ subclass.set :app_file, caller_files.first unless subclass.app_file?
1018
+ super
1019
+ end
1020
+
1021
+ @@mutex = Mutex.new
1022
+ def synchronize(&block)
1023
+ if lock?
1024
+ @@mutex.synchronize(&block)
1025
+ else
1026
+ yield
1027
+ end
1028
+ end
1029
+
1030
+ # used for deprecation warnings
1031
+ def warn(message)
1032
+ super message + "\n\tfrom #{cleaned_caller.first.join(':')}"
1033
+ end
1034
+
1035
+ # Like Kernel#caller but excluding certain magic entries
1036
+ def cleaned_caller(keep = 3)
1037
+ caller(1).
1038
+ map! { |line| line.split(/:(?=\d|in )/, 3)[0,keep] }.
1039
+ reject { |file, *_| CALLERS_TO_IGNORE.any? { |pattern| file =~ pattern } }
1040
+ end
1041
+ end
1042
+
1043
+ # Fixes encoding issues by
1044
+ # * defaulting to UTF-8
1045
+ # * casting params to Encoding.default_external
1046
+ #
1047
+ # The latter might not be necessary if Rack handles it one day.
1048
+ # Keep an eye on Rack's LH #100.
1049
+ def force_encoding(*args) settings.force_encoding(*args) end
1050
+ if defined? Encoding
1051
+ def self.force_encoding(data, encoding = default_encoding)
1052
+ return if data == settings || data.is_a?(Tempfile)
1053
+ if data.respond_to? :force_encoding
1054
+ data.force_encoding(encoding).encode!
1055
+ elsif data.respond_to? :each_value
1056
+ data.each_value { |v| force_encoding(v, encoding) }
1057
+ elsif data.respond_to? :each
1058
+ data.each { |v| force_encoding(v, encoding) }
1059
+ end
1060
+ data
1061
+ end
1062
+ else
1063
+ def self.force_encoding(data, *) data end
1064
+ end
1065
+
1066
+ reset!
1067
+
1068
+ set :environment, (ENV['RACK_ENV'] || :development).to_sym
1069
+ set :raise_errors, Proc.new { test? }
1070
+ set :dump_errors, Proc.new { !test? }
1071
+ set :show_exceptions, Proc.new { development? }
1072
+ set :sessions, false
1073
+ set :logging, false
1074
+ set :protection, true
1075
+ set :method_override, false
1076
+ set :use_code, false
1077
+ set :default_encoding, "utf-8"
1078
+ set :x_cascade, true
1079
+ set :add_charset, %w[javascript xml xhtml+xml json].map { |t| "application/#{t}" }
1080
+ settings.add_charset << /^text\//
1081
+
1082
+ # explicitly generating a session secret eagerly to play nice with preforking
1083
+ begin
1084
+ require 'securerandom'
1085
+ set :session_secret, SecureRandom.hex(64)
1086
+ rescue LoadError, NotImplementedError
1087
+ # SecureRandom raises a NotImplementedError if no random device is available
1088
+ set :session_secret, "%064x" % Kernel.rand(2**256-1)
1089
+ end
1090
+
1091
+ class << self
1092
+ alias_method :methodoverride?, :method_override?
1093
+ alias_method :methodoverride=, :method_override=
1094
+ end
1095
+
1096
+ set :run, false # start server via at-exit hook?
1097
+ set :running_server, nil
1098
+ set :handler_name, nil
1099
+ set :traps, true
1100
+ set :server, %w[HTTP webrick]
1101
+ set :bind, Proc.new { development? ? 'localhost' : '0.0.0.0' }
1102
+ set :port, Integer(ENV['PORT'] && !ENV['PORT'].empty? ? ENV['PORT'] : 4567)
1103
+
1104
+ ruby_engine = defined?(RUBY_ENGINE) && RUBY_ENGINE
1105
+
1106
+ if ruby_engine == 'macruby'
1107
+ server.unshift 'control_tower'
1108
+ else
1109
+ server.unshift 'reel'
1110
+ server.unshift 'mongrel' if ruby_engine.nil?
1111
+ server.unshift 'puma' if ruby_engine != 'rbx'
1112
+ server.unshift 'thin' if ruby_engine != 'jruby'
1113
+ server.unshift 'puma' if ruby_engine == 'rbx'
1114
+ server.unshift 'trinidad' if ruby_engine == 'jruby'
1115
+ end
1116
+
1117
+ set :absolute_redirects, true
1118
+ set :prefixed_redirects, false
1119
+ set :empty_path_info, nil
1120
+
1121
+ set :app_file, nil
1122
+ set :root, Proc.new { app_file && File.expand_path(File.dirname(app_file)) }
1123
+ set :views, Proc.new { root && File.join(root, 'views') }
1124
+ set :reload_templates, Proc.new { development? }
1125
+ set :lock, false
1126
+ set :threaded, true
1127
+
1128
+ set :public_folder, Proc.new { root && File.join(root, 'public') }
1129
+ set :static, Proc.new { public_folder && File.exist?(public_folder) }
1130
+ set :static_cache_control, false
1131
+
1132
+ error ::Exception do
1133
+ response.status = 500
1134
+ content_type 'text/html'
1135
+ '<h1>Internal Server Error</h1>'
1136
+ end
1137
+
1138
+ after do
1139
+ env['sinarey.params'] ||= @params
1140
+ end
1141
+
1142
+ configure :development do
1143
+ get '/__sinatra__/:image.png' do
1144
+ filename = File.dirname(__FILE__) + "/images/#{params[:image]}.png"
1145
+ content_type :png
1146
+ send_file filename
1147
+ end
1148
+
1149
+ error NotFound do
1150
+ content_type 'text/html'
1151
+
1152
+ if self.class == Sinatra::Application
1153
+ code = <<-RUBY.gsub(/^ {12}/, '')
1154
+ #{request.request_method.downcase} '#{request.path_info}' do
1155
+ "Hello World"
1156
+ end
1157
+ RUBY
1158
+ else
1159
+ code = <<-RUBY.gsub(/^ {12}/, '')
1160
+ class #{self.class}
1161
+ #{request.request_method.downcase} '#{request.path_info}' do
1162
+ "Hello World"
1163
+ end
1164
+ end
1165
+ RUBY
1166
+
1167
+ file = settings.app_file.to_s.sub(settings.root.to_s, '').sub(/^\//, '')
1168
+ code = "# in #{file}\n#{code}" unless file.empty?
1169
+ end
1170
+
1171
+ (<<-HTML).gsub(/^ {10}/, '')
1172
+ <!DOCTYPE html>
1173
+ <html>
1174
+ <head>
1175
+ <style type="text/css">
1176
+ body { text-align:center;font-family:helvetica,arial;font-size:22px;
1177
+ color:#888;margin:20px}
1178
+ #c {margin:0 auto;width:500px;text-align:left}
1179
+ </style>
1180
+ </head>
1181
+ <body>
1182
+ <h2>Sinatra doesn&rsquo;t know this ditty.</h2>
1183
+ <img src='#{uri "/__sinatra__/404.png"}'>
1184
+ <div id="c">
1185
+ Try this:
1186
+ <pre>#{code}</pre>
1187
+ </div>
1188
+ </body>
1189
+ </html>
1190
+ HTML
1191
+ end
1192
+ end
1193
+ end
1194
+
1220
1195
  end