sinarey 1.0.4 → 1.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.
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