padrino-core 0.9.10 → 0.9.11

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,7 +1,5 @@
1
- require 'usher' unless defined?(Usher)
2
- require 'padrino-core/support_lite' unless String.method_defined?(:blank?)
3
-
4
- Usher::Route.class_eval { attr_accessor :custom_conditions, :before_filters, :after_filters, :use_layout }
1
+ require 'http_router' unless defined?(HttpRouter)
2
+ require 'padrino-core/support_lite' unless defined?(SupportLite)
5
3
 
6
4
  module Padrino
7
5
  ##
@@ -12,110 +10,38 @@ module Padrino
12
10
  # to the url throughout the application.
13
11
  #
14
12
  module Routing
15
- class UnrecognizedException < RuntimeError #:nodoc:
16
- end
13
+ CONTENT_TYPE_ALIASES = { :htm => :html }
17
14
 
18
- def self.registered(app)
19
- app.send(:include, Padrino::Routing)
15
+ class ::HttpRouter::Route
16
+ attr_accessor :custom_conditions, :before_filters, :after_filters, :use_layout, :controller
20
17
  end
21
18
 
22
- def self.included(base)
23
- base.extend Padrino::Routing::ClassMethods
24
- end
19
+ class ::Sinatra::Request
20
+ attr_accessor :match
25
21
 
26
- ##
27
- # Instance method for url generation like:
28
- #
29
- # ==== Examples
30
- #
31
- # url(:show, :id => 1)
32
- # url(:show, :name => :test)
33
- # url("/show/:id/:name", :id => 1, :name => foo)
34
- #
35
- def url(*names)
36
- self.class.url(*names)
37
- end
38
- alias :url_for :url
22
+ def controller
23
+ route && route.controller
24
+ end
39
25
 
40
- ##
41
- # This is mostly just a helper so request.path_info isn't changed when
42
- # serving files from the public directory
43
- #
44
- def static_file?(path_info)
45
- return if (public_dir = settings.public).nil?
46
- public_dir = File.expand_path(public_dir)
47
-
48
- path = File.expand_path(public_dir + unescape(path_info))
49
- return if path[0, public_dir.length] != public_dir
50
- return unless File.file?(path)
51
- return path
26
+ def route
27
+ match.matched? ? match.path.route : nil
28
+ end
52
29
  end
53
30
 
54
- ##
55
- # Return the request format, this is useful when we need to respond to a given content_type like:
56
- #
57
- # ==== Examples
58
- #
59
- # get :index, :provides => :any do
60
- # case content_type
61
- # when :js then ...
62
- # when :json then ...
63
- # when :html then ...
64
- # end
65
- # end
66
- #
67
- def content_type(type=nil, params={})
68
- type.nil? ? @_content_type : super(type, params)
31
+ class UnrecognizedException < RuntimeError #:nodoc:
69
32
  end
70
33
 
71
34
  ##
72
- # Method for deliver static files.
35
+ # Main class that register this extension
73
36
  #
74
- def static!
75
- if path = static_file?(request.path_info)
76
- env['sinatra.static_file'] = path
77
- send_file(path, :disposition => nil)
37
+ class << self
38
+ def registered(app)
39
+ app.send(:include, InstanceMethods)
40
+ app.extend(ClassMethods)
78
41
  end
42
+ alias :included :registered
79
43
  end
80
44
 
81
- private
82
- ##
83
- # Compatibility with usher
84
- #
85
- def route!(base=self.class, pass_block=nil)
86
- if base.router and match = base.router.recognize(@request, @request.path_info)
87
- @block_params = match.params.map { |p| p.last }
88
- (@params ||= {}).merge!(match.params_as_hash)
89
- pass_block = catch(:pass) do
90
- # Run Sinatra Conditions
91
- match.path.route.custom_conditions.each { |cond| throw :pass if instance_eval(&cond) == false }
92
- # Run scoped before filters
93
- match.path.route.before_filters.each { |bef| throw :pass if instance_eval(&bef) == false }
94
- # If present set current controller layout
95
- parent_layout = base.instance_variable_get(:@layout)
96
- base.instance_variable_set(:@layout, match.path.route.use_layout) if match.path.route.use_layout
97
- # Now we can eval route, but because we have "throw halt" we need to be
98
- # (en)sure to reset old layout and run controller after filters.
99
- begin
100
- route_eval(&match.destination)
101
- ensure
102
- base.instance_variable_set(:@layout, parent_layout) if match.path.route.use_layout
103
- match.path.route.after_filters.each { |aft| throw :pass if instance_eval(&aft) == false }
104
- end
105
- end
106
- end
107
-
108
- # Run routes defined in superclass.
109
- if base.superclass.respond_to?(:router)
110
- route! base.superclass, pass_block
111
- return
112
- end
113
-
114
- route_eval(&pass_block) if pass_block
115
-
116
- route_missing
117
- end
118
-
119
45
  module ClassMethods
120
46
  ##
121
47
  # Method for organize in a better way our routes like:
@@ -137,17 +63,38 @@ module Padrino
137
63
  # get "/show/:id" do; ...; end
138
64
  # end
139
65
  #
140
- # You can supply default values:
141
- #
142
- # controller :lang => :de do
143
- # get :index, :map => "/:lang" do; ...; end
144
- # end
145
- #
146
66
  # and you can call directly these urls:
147
67
  #
148
68
  # # => "/admin"
149
69
  # # => "/admin/show/1"
150
70
  #
71
+ # You can supply provides to all controller routes:
72
+ #
73
+ # controller :provides => [:html, :xml, :json] do
74
+ # get :index do; "respond to html, xml and json"; end
75
+ # post :index do; "respond to html, xml and json"; end
76
+ # get :foo do; "respond to html, xml and json"; end
77
+ # end
78
+ #
79
+ # You can specify parent resources in padrino with the :parent option on the controller:
80
+ #
81
+ # controllers :product, :parent => :user do
82
+ # get :index do
83
+ # # url is generated as "/user/#{params[:user_id]}/product"
84
+ # # url_for(:product, :index, :user_id => 5) => "/user/5/product"
85
+ # end
86
+ # get :show, :with => :id do
87
+ # # url is generated as "/user/#{params[:user_id]}/product/show/#{params[:id]}"
88
+ # # url_for(:product, :show, :user_id => 5, :id => 10) => "/user/5/product/show/10"
89
+ # end
90
+ # end
91
+ #
92
+ # You can supply default values:
93
+ #
94
+ # controller :lang => :de do
95
+ # get :index, :map => "/:lang" do; "params[:lang] == :de"; end
96
+ # end
97
+ #
151
98
  # In a controller before and after filters are scoped and didn't affect other controllers or main app.
152
99
  # In a controller layout are scoped and didn't affect others controllers and main app.
153
100
  #
@@ -164,6 +111,7 @@ module Padrino
164
111
  # Controller defaults
165
112
  @_controller, original_controller = args, @_controller
166
113
  @_parents, original_parent = options.delete(:parent), @_parents
114
+ @_provides, original_provides = options.delete(:provides), @_provides
167
115
  @_defaults, original_defaults = options, @_defaults
168
116
 
169
117
  # Application defaults
@@ -179,7 +127,8 @@ module Padrino
179
127
  @layout = original_layout
180
128
 
181
129
  # Controller defaults
182
- @_controller, @_parents, @_defaults = original_controller, original_parent, original_defaults
130
+ @_controller, @_parents = original_controller, original_parent
131
+ @_defaults, @_provides = original_defaults, original_provides
183
132
  else
184
133
  include(*args) if extensions.any?
185
134
  end
@@ -187,17 +136,17 @@ module Padrino
187
136
  alias :controllers :controller
188
137
 
189
138
  ##
190
- # Usher router, for fatures and configurations see: http://github.com/joshbuddy/usher
139
+ # Using HTTPRouter, for features and configurations see: http://github.com/joshbuddy/http_router
191
140
  #
192
141
  # ==== Examples
193
142
  #
194
- # router.add_route('/greedy/{!:greed,.*}')
195
- # router.recognize_path('/simple')
143
+ # router.add('/greedy/{!:greed,.*}')
144
+ # router.recognize('/simple')
196
145
  #
197
146
  def router
198
- @router ||= Usher.new(:request_methods => [:request_method, :host, :port, :scheme],
199
- :ignore_trailing_delimiters => true,
200
- :generator => Usher::Util::Generators::URL.new)
147
+ unless @router
148
+ @router = HttpRouter.new
149
+ end
201
150
  block_given? ? yield(@router) : @router
202
151
  end
203
152
  alias :urls :router
@@ -208,27 +157,40 @@ module Padrino
208
157
  # ==== Examples
209
158
  #
210
159
  # url(:show, :id => 1)
160
+ # url(:show, 1)
211
161
  # url(:show, :name => :test)
212
162
  # url("/show/:id/:name", :id => 1, :name => foo)
213
163
  #
214
- def url(*names)
215
- params = names.extract_options! # parameters is hash at end
164
+ def url(*args)
165
+ params = args.extract_options! # parameters is hash at end
166
+ names, params_array = args.partition{|a| a.is_a?(Symbol)}
216
167
  name = names.join("_").to_sym # route name is concatenated with underscores
217
168
  if params.is_a?(Hash)
218
169
  params[:format] = params[:format].to_s if params.has_key?(:format)
219
170
  params.each { |k,v| params[k] = v.to_param if v.respond_to?(:to_param) }
220
171
  end
221
- url = router.generator.generate(name, params)
222
- url = File.join(uri_root, url) if defined?(uri_root) && uri_root != "/"
223
- url = File.join(ENV['RACK_BASE_URI'].to_s, url) if ENV['RACK_BASE_URI']
172
+ url = if params_array.empty?
173
+ router.url(name, params)
174
+ else
175
+ router.url(name, *(params_array << params))
176
+ end
177
+ url[0,0] = conform_uri(uri_root) if defined?(uri_root)
178
+ url[0,0] = conform_uri(ENV['RACK_BASE_URI']) if ENV['RACK_BASE_URI']
179
+ url = "/" if url.blank?
224
180
  url
225
- rescue Usher::UnrecognizedException
181
+ rescue HttpRouter::UngeneratableRouteException
226
182
  route_error = "route mapping for url(#{name.inspect}) could not be found!"
227
183
  raise Padrino::Routing::UnrecognizedException.new(route_error)
228
184
  end
229
185
  alias :url_for :url
230
186
 
231
187
  private
188
+
189
+ # Add prefix slash if its not present and remove trailing slashes.
190
+ def conform_uri(uri_string)
191
+ uri_string.gsub(/^(?!\/)(.*)/, '/\1').gsub(/[\/]+$/, '')
192
+ end
193
+
232
194
  ##
233
195
  # Rewrite default because now routes can be:
234
196
  #
@@ -242,22 +204,16 @@ module Padrino
242
204
  # get :show, :with => :id, :parent => :user # => "/user/:user_id/show/:id"
243
205
  # get :show, :with => :id # => "/show/:id"
244
206
  # get :show, :with => [:id, :name] # => "/show/:id/:name"
245
- # get :list, :provides => :js # => "/list.{:format,js)"
246
- # get :list, :provides => :any # => "/list(.:format)"
247
- # get :list, :provides => [:js, :json] # => "/list.{!format,js|json}"
248
- # get :list, :provides => [:html, :js, :json] # => "/list(.{!format,js|json})"
207
+ # get :list, :provides => :js # => "/list.{:format,js)"
208
+ # get :list, :provides => :any # => "/list(.:format)"
209
+ # get :list, :provides => [:js, :json] # => "/list.{!format,js|json}"
210
+ # get :list, :provides => [:html, :js, :json] # => "/list(.{!format,js|json})"
249
211
  #
250
212
  def route(verb, path, options={}, &block)
251
213
  # Do padrino parsing. We dup options so we can build HEAD request correctly
252
- path, name, options = *parse_route(path, options.dup)
253
-
254
- # Usher Conditions
255
- options[:conditions] ||= {}
256
- options[:conditions][:request_method] = verb
257
- options[:conditions][:host] = options.delete(:host) if options.key?(:host)
258
-
259
- # Because of self.options.host
260
- host_name(options.delete(:host)) if options.key?(:host)
214
+ route_options = options.dup
215
+ route_options[:provides] = @_provides if @_provides
216
+ path, name, options = *parse_route(path, route_options, verb)
261
217
 
262
218
  # Sinatra defaults
263
219
  define_method "#{verb} #{path}", &block
@@ -270,9 +226,14 @@ module Padrino
270
226
  end
271
227
  invoke_hook(:route_added, verb, path, block)
272
228
 
273
- # Usher route
274
- route = router.add_route(path, options).to(block)
229
+ # HTTPRouter route construction
230
+ route = router.add(path)
275
231
  route.name(name) if name
232
+ route.send(verb.downcase.to_sym)
233
+ route.host(options.delete(:host)) if options.key?(:host)
234
+ route.default_values = options.delete(:default_values)
235
+
236
+ route.to(block)
276
237
 
277
238
  # Add Sinatra conditions
278
239
  options.each { |option, args| send(option, *args) }
@@ -284,6 +245,7 @@ module Padrino
284
245
  route.before_filters = @before_filters
285
246
  route.after_filters = @after_filters
286
247
  route.use_layout = @layout
248
+ route.controller = Array(@_controller).first.to_s
287
249
  else
288
250
  route.before_filters = []
289
251
  route.after_filters = []
@@ -298,7 +260,7 @@ module Padrino
298
260
  # is parsed to reflect provides formats, controllers, parents, 'with' parameters,
299
261
  # and other options.
300
262
  #
301
- def parse_route(path, options)
263
+ def parse_route(path, options, verb)
302
264
  # We need save our originals path/options so we can perform correctly cache.
303
265
  original = [path, options.dup]
304
266
 
@@ -311,17 +273,28 @@ module Padrino
311
273
  end
312
274
 
313
275
  if path.kind_of?(String) # path i.e "/index" or "/show"
276
+ # Backwards compatability
277
+
278
+ if path == '(/)'
279
+ path = '/'
280
+ warn "WARNING! #{Padrino.first_caller}: #{verb} (/) is deprecated, simply use / instead" if verb != "HEAD"
281
+ end
282
+
283
+ if path =~ /\(\/\)$/
284
+ path.gsub(/\(\/\)$/, '/?')
285
+ warn "WARNING! #{Padrino.first_caller}: #{verb} (/) is deprecated, simply use /? instead" if verb != "HEAD"
286
+ end
287
+
314
288
  # Now we need to parse our 'with' params
315
289
  if with_params = options.delete(:with)
316
290
  path = process_path_for_with_params(path, with_params)
317
291
  end
318
292
 
319
- # Now we need to parse our provides with :respond_to backward compatibility
320
- options[:provides] ||= options.delete(:respond_to)
293
+ # Now we need to parse our provides
321
294
  options.delete(:provides) if options[:provides].nil?
322
295
 
323
296
  if format_params = options[:provides]
324
- path = process_path_for_provides(path, format_params)
297
+ process_path_for_provides(path, format_params)
325
298
  end
326
299
 
327
300
  # Build our controller
@@ -331,6 +304,7 @@ module Padrino
331
304
  # Now we need to add our controller path only if not mapped directly
332
305
  if map.blank?
333
306
  controller_path = controller.join("/")
307
+ path.gsub!(%r{^\(/\)|/\?}, "")
334
308
  path = File.join(controller_path, path)
335
309
  end
336
310
  # Here we build the correct name route
@@ -347,17 +321,9 @@ module Padrino
347
321
  end
348
322
 
349
323
  # Small reformats
350
- path.gsub!(/\/?index\/?/, '') # Remove index
351
- path = (uri_root == "/" ? "/" : "(/)") if path.blank? # Add a trailing delimiter if path is empty
352
-
353
- # We need to have a path that start with / in some circumstances and that don't end with /
354
- if path != "(/)" && path != "/"
355
- path = "/" + path unless path =~ %r{^/}
356
- path.sub!(%r{/$}, '')
357
- end
358
-
359
- # We need to fix a few differences between the usher and sintra router
360
- path.sub!(%r{/\?$}, '(/)') # '/foo/?' => '/foo(/)'
324
+ path.gsub!(%r{/?index/?}, '') # Remove index path
325
+ path[0,0] = "/" if path !~ %r{^\(?/} && path # Paths must start with a /
326
+ path.sub!(%r{/$}, '') if path != "/" # Remove latest trailing delimiter
361
327
  end
362
328
 
363
329
  # Merge in option defaults
@@ -388,31 +354,144 @@ module Padrino
388
354
  # Used for calculating path in route method
389
355
  #
390
356
  def process_path_for_provides(path, format_params)
391
- path + "(.:format)"
357
+ path << "(.:format)" unless path[-10, 10] == '(.:format)'
392
358
  end
393
359
 
394
360
  ##
395
361
  # Allow paths for the given request head or request format
396
362
  #
397
363
  def provides(*types)
398
- mime_types = types.map{ |t| mime_type(t) }
364
+ mime_types = types.map { |t| mime_type(t) }
399
365
 
400
- condition {
401
- matching_types = (request.accept & mime_types)
366
+ condition do
367
+ accepts = request.accept.map { |a| a.split(";")[0].strip }
368
+ matching_types = (accepts & mime_types)
402
369
  request.path_info =~ /\.([^\.\/]+)$/
403
- format = ($1 || :html).to_sym
404
- match_format = types.include?(format) || types.include?(:any)
405
- @_content_type =
406
- if mime_type = matching_types.first
407
- Rack::Mime::MIME_TYPES.find { |k, v| v == matching_types.first }[0].sub(/\./,'').to_sym
408
- else
409
- format
370
+ url_format = $1.to_sym if $1
371
+
372
+ if !url_format && matching_types.first
373
+ type = Rack::Mime::MIME_TYPES.find { |k, v| v == matching_types.first }[0].sub(/\./,'').to_sym
374
+ accept_format = CONTENT_TYPE_ALIASES[type] || type
375
+ end
376
+
377
+ matched_format = types.include?(:any) ||
378
+ types.include?(accept_format) ||
379
+ types.include?(url_format) ||
380
+ accepts.any? { |a| a == "*/*" } ||
381
+ (request.accept.empty? && types.include?(:html))
382
+
383
+ if matched_format
384
+ @_content_type = url_format || accept_format || :html
385
+ content_type(@_content_type, :charset => 'utf-8')
386
+ end
387
+
388
+ matched_format
389
+ end
390
+ end
391
+ end
392
+
393
+ module InstanceMethods
394
+ ##
395
+ # Instance method for url generation like:
396
+ #
397
+ # ==== Examples
398
+ #
399
+ # url(:show, :id => 1)
400
+ # url(:show, :name => :test)
401
+ # url("/show/:id/:name", :id => 1, :name => foo)
402
+ #
403
+ def url(*args)
404
+ self.class.url(*args)
405
+ end
406
+ alias :url_for :url
407
+
408
+ ##
409
+ # This is mostly just a helper so request.path_info isn't changed when
410
+ # serving files from the public directory
411
+ #
412
+ def static_file?(path_info)
413
+ return if (public_dir = settings.public).nil?
414
+ public_dir = File.expand_path(public_dir)
415
+
416
+ path = File.expand_path(public_dir + unescape(path_info))
417
+ return if path[0, public_dir.length] != public_dir
418
+ return unless File.file?(path)
419
+ return path
420
+ end
421
+
422
+ ##
423
+ # Method for deliver static files.
424
+ #
425
+ def static!
426
+ if path = static_file?(request.path_info)
427
+ env['sinatra.static_file'] = path
428
+ send_file(path, :disposition => nil)
429
+ end
430
+ end
431
+
432
+ ##
433
+ # Return the request format, this is useful when we need to respond to a given content_type like:
434
+ #
435
+ # ==== Examples
436
+ #
437
+ # get :index, :provides => :any do
438
+ # case content_type
439
+ # when :js then ...
440
+ # when :json then ...
441
+ # when :html then ...
442
+ # end
443
+ # end
444
+ #
445
+ def content_type(type=nil, params={})
446
+ type.nil? ? @_content_type : super(type, params)
447
+ end
448
+
449
+ private
450
+ ##
451
+ # Compatibility with http_router
452
+ #
453
+ def route!(base=self.class, pass_block=nil)
454
+ if base.router and match = base.router.recognize(@request)
455
+ if !match.matched?
456
+ route_eval {
457
+ match.headers.each{|k,v| response[k] = v}
458
+ status match.status
459
+ }
460
+ elsif match
461
+ @block_params = match.params
462
+ (@params ||= {}).merge!(match.params_as_hash)
463
+ pass_block = catch(:pass) do
464
+ # Run Sinatra Conditions
465
+ match.path.route.custom_conditions.each { |cond| throw :pass if instance_eval(&cond) == false }
466
+ # Run scoped before filters
467
+ match.path.route.before_filters.each { |bef| throw :pass if instance_eval(&bef) == false }
468
+ # If present set current controller layout
469
+ parent_layout = base.instance_variable_get(:@layout)
470
+ base.instance_variable_set(:@layout, match.path.route.use_layout) if match.path.route.use_layout
471
+ # Provide access to the current controller to the request
472
+ request.match = match
473
+ # Now we can eval route, but because we have "throw halt" we need to be
474
+ # (en)sure to reset old layout and run controller after filters.
475
+ begin
476
+ route_eval(&match.destination)
477
+ ensure
478
+ base.instance_variable_set(:@layout, parent_layout) if match.path.route.use_layout
479
+ match.path.route.after_filters.each { |aft| throw :pass if instance_eval(&aft) == false }
480
+ end
410
481
  end
411
- content_type(@_content_type, :charset => 'utf-8')
412
- match_format || !matching_types.empty?
413
- }
482
+ end
483
+ end
484
+
485
+ # Run routes defined in superclass.
486
+ if base.superclass.respond_to?(:router)
487
+ route! base.superclass, pass_block
488
+ return
489
+ end
490
+
491
+ route_eval(&pass_block) if pass_block
492
+
493
+ route_missing
414
494
  end
415
- alias :respond_to :provides
416
- end # ClassMethods
495
+ end # InstanceMethods
417
496
  end # Routing
418
497
  end # Padrino