pendragon 0.3.0

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.
@@ -0,0 +1,15 @@
1
+ require 'pendragon/padrino/router'
2
+ require 'pendragon/padrino/route'
3
+ require 'pendragon/padrino/ext/instance_methods'
4
+ require 'pendragon/padrino/ext/class_methods'
5
+
6
+ module Pendragon
7
+ module Padrino
8
+ class << self
9
+ def registered(app)
10
+ app.extend(ClassMethods)
11
+ app.send(:include, InstanceMethods)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,306 @@
1
+ module Pendragon
2
+ module Padrino
3
+ module ClassMethods
4
+ CONTENT_TYPE_ALIASES = {:htm => :html} unless defined?(CONTENT_TYPE_ALIASES)
5
+ ROUTE_PRIORITY = {:high => 0, :normal => 1, :low => 2} unless defined?(ROUTE_PRIORITY)
6
+
7
+ def router
8
+ @router ||= ::Pendragon::Padrino::Router.new
9
+ block_given? ? yield(@router) : @router
10
+ end
11
+
12
+ def compiled_router
13
+ if @deferred_routes
14
+ deferred_routes.each do |routes|
15
+ routes.each do |(route, dest)|
16
+ route.to(&dest)
17
+ route.before_filters.flatten!
18
+ route.after_filters.flatten!
19
+ end
20
+ end
21
+ @deferred_routes = nil
22
+ end
23
+ router
24
+ end
25
+
26
+ def deferred_routes
27
+ @deferred_routes ||= ROUTE_PRIORITY.map{[]}
28
+ end
29
+
30
+ def url(*args)
31
+ params = args.extract_options! # parameters is hash at end
32
+ names, params_array = args.partition{|a| a.is_a?(Symbol)}
33
+ name = names[0, 2].join(" ").to_sym # route name is concatenated with underscores
34
+ if params.is_a?(Hash)
35
+ params[:format] = params[:format].to_s unless params[:format].nil?
36
+ params = value_to_param(params)
37
+ end
38
+ url =
39
+ if params_array.empty?
40
+ compiled_router.path(name, params)
41
+ else
42
+ compiled_router.path(name, *(params_array << params))
43
+ end
44
+ rebase_url(url)
45
+ rescue Pendragon::InvalidRouteException
46
+ route_error = "route mapping for url(#{name.inspect}) could not be found!"
47
+ raise ::Padrino::Routing::UnrecognizedException.new(route_error)
48
+ end
49
+ alias :url_for :url
50
+
51
+ def recognize_path(path)
52
+ responses = @router.recognize_path(path)
53
+ [responses[0], responses[1]]
54
+ end
55
+
56
+ def rebase_url(url)
57
+ if url.start_with?('/')
58
+ new_url = ''
59
+ new_url << conform_uri(uri_root) if defined?(uri_root)
60
+ new_url << conform_uri(ENV['RACK_BASE_URI']) if ENV['RACK_BASE_URI']
61
+ new_url << url
62
+ else
63
+ url.blank? ? '/' : url
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ def route(verb, path, *args, &block)
70
+ options = case args.size
71
+ when 2
72
+ args.last.merge(:map => args.first)
73
+ when 1
74
+ map = args.shift if args.first.is_a?(String)
75
+ if args.first.is_a?(Hash)
76
+ map ? args.first.merge(:map => map) : args.first
77
+ else
78
+ {:map => map || args.first}
79
+ end
80
+ when 0
81
+ {}
82
+ else raise
83
+ end
84
+
85
+ route_options = options.dup
86
+ route_options[:provides] = @_provides if @_provides
87
+
88
+ if allow_disabled_csrf
89
+ unless route_options[:csrf_protection] == false
90
+ route_options[:csrf_protection] = true
91
+ end
92
+ end
93
+
94
+ path, *route_options[:with] = path if path.is_a?(Array)
95
+ action = path
96
+ path, name, route_parents, options, route_options = *parse_route(path, route_options, verb)
97
+ options.reverse_merge!(@_conditions) if @_conditions
98
+
99
+ method_name = "#{verb} #{path}"
100
+ unbound_method = generate_method(method_name, &block)
101
+
102
+ block = block.arity != 0 ?
103
+ proc {|a,p| unbound_method.bind(a).call(*p) } :
104
+ proc {|a,p| unbound_method.bind(a).call }
105
+
106
+ invoke_hook(:route_added, verb, path, block)
107
+
108
+ # Pendragon route construction
109
+ path[0, 0] = "/" if path == "(.:format)?"
110
+ route_options.merge!(:name => name) if name
111
+ route = router.add(verb.downcase.to_sym, path, route_options)
112
+ route.action = action
113
+ priority_name = options.delete(:priority) || :normal
114
+ priority = ROUTE_PRIORITY[priority_name] or raise("Priority #{priority_name} not recognized, try #{ROUTE_PRIORITY.keys.join(', ')}")
115
+ route.cache = options.key?(:cache) ? options.delete(:cache) : @_cache
116
+ route.parent = route_parents ? (route_parents.count == 1 ? route_parents.first : route_parents) : route_parents
117
+ route.host = options.delete(:host) if options.key?(:host)
118
+ route.user_agent = options.delete(:agent) if options.key?(:agent)
119
+ if options.key?(:default_values)
120
+ defaults = options.delete(:default_values)
121
+ route.options[:default_values] = defaults if defaults
122
+ end
123
+ options.delete_if do |option, captures|
124
+ if route.significant_variable_names.include?(option)
125
+ route.capture[option] = Array(captures).first
126
+ true
127
+ end
128
+ end
129
+
130
+ # Add Sinatra conditions
131
+ options.each {|o, a| route.respond_to?("#{o}=") ? route.send("#{o}=", a) : send(o, *a) }
132
+ conditions, @conditions = @conditions, []
133
+ route.custom_conditions.concat(conditions)
134
+
135
+ invoke_hook(:padrino_route_added, route, verb, path, args, options, block)
136
+
137
+ # Add Application defaults
138
+ route.before_filters << @filters[:before]
139
+ route.after_filters << @filters[:after]
140
+ if @_controller
141
+ route.use_layout = @layout
142
+ route.controller = Array(@_controller)[0].to_s
143
+ end
144
+
145
+ deferred_routes[priority] << [route, block]
146
+
147
+ route
148
+ end
149
+
150
+ def parse_route(path, options, verb)
151
+ route_options = {}
152
+
153
+ # We need check if path is a symbol, if that it's a named route.
154
+ map = options.delete(:map)
155
+
156
+ # path i.e :index or :show
157
+ if path.kind_of?(Symbol)
158
+ name = path
159
+ path = map ? map.dup : (path == :index ? '/' : path.to_s)
160
+ end
161
+
162
+ # Build our controller
163
+ controller = Array(@_controller).map(&:to_s)
164
+
165
+ case path
166
+ when String # path i.e "/index" or "/show"
167
+ # Now we need to parse our 'with' params
168
+ if with_params = options.delete(:with)
169
+ path = process_path_for_with_params(path, with_params)
170
+ end
171
+
172
+ # Now we need to parse our provides
173
+ options.delete(:provides) if options[:provides].nil?
174
+
175
+ if @_use_format or format_params = options[:provides]
176
+ process_path_for_provides(path, format_params)
177
+ # options[:add_match_with] ||= {}
178
+ # options[:add_match_with][:format] = /[^\.]+/
179
+ end
180
+
181
+ absolute_map = map && map[0] == ?/
182
+
183
+ unless controller.empty?
184
+ # Now we need to add our controller path only if not mapped directly
185
+ if map.blank? and !absolute_map
186
+ controller_path = controller.join("/")
187
+ path.gsub!(%r{^\(/\)|/\?}, "")
188
+ path = File.join(controller_path, path) unless @_map
189
+ end
190
+ end
191
+
192
+ # Now we need to parse our 'parent' params and parent scope.
193
+ if !absolute_map and parent_params = options.delete(:parent) || @_parents
194
+ parent_params = (Array(@_parents) + Array(parent_params)).uniq
195
+ path = process_path_for_parent_params(path, parent_params)
196
+ end
197
+
198
+ # Add any controller level map to the front of the path.
199
+ path = "#{@_map}/#{path}".squeeze('/') unless absolute_map or @_map.blank?
200
+
201
+ # Small reformats
202
+ path.gsub!(%r{/\?$}, '(/)') # Remove index path
203
+ path.gsub!(%r{//$}, '/') # Remove index path
204
+ path[0,0] = "/" if path !~ %r{^\(?/} # Paths must start with a /
205
+ path.sub!(%r{/(\))?$}, '\\1') if path != "/" # Remove latest trailing delimiter
206
+ path.gsub!(/\/(\(\.|$)/, '\\1') # Remove trailing slashes
207
+ path.squeeze!('/')
208
+ when Regexp
209
+ route_options[:path_for_generation] = options.delete(:generate_with) if options.key?(:generate_with)
210
+ end
211
+
212
+ name = options.delete(:route_name) if name.nil? && options.key?(:route_name)
213
+ name = options.delete(:name) if name.nil? && options.key?(:name)
214
+ if name
215
+ controller_name = controller.join("_")
216
+ name = "#{controller_name} #{name}".to_sym unless controller_name.blank?
217
+ end
218
+
219
+ # Merge in option defaults.
220
+ options.reverse_merge!(:default_values => @_defaults)
221
+
222
+ [path, name, parent_params, options, route_options]
223
+ end
224
+
225
+ def provides(*types)
226
+ @_use_format = true
227
+ condition do
228
+ mime_types = types.map {|t| mime_type(t) }.compact
229
+ url_format = params[:format].to_sym if params[:format]
230
+ accepts = request.accept.map {|a| a.to_str }
231
+ accepts = [] if accepts == ["*/*"]
232
+
233
+ # per rfc2616-sec14:
234
+ # Assume */* if no ACCEPT header is given.
235
+ catch_all = (accepts.delete "*/*" || accepts.empty?)
236
+ matching_types = accepts.empty? ? mime_types.slice(0,1) : (accepts & mime_types)
237
+ if matching_types.empty? && types.include?(:any)
238
+ matching_types = accepts
239
+ end
240
+
241
+ if !url_format && matching_types.first
242
+ type = ::Rack::Mime::MIME_TYPES.find {|k, v| v == matching_types.first }[0].sub(/\./,'').to_sym
243
+ accept_format = CONTENT_TYPE_ALIASES[type] || type
244
+ elsif catch_all && !types.include?(:any)
245
+ type = types.first
246
+ accept_format = CONTENT_TYPE_ALIASES[type] || type
247
+ end
248
+
249
+ matched_format = types.include?(:any) ||
250
+ types.include?(accept_format) ||
251
+ types.include?(url_format) ||
252
+ ((!url_format) && request.accept.empty? && types.include?(:html))
253
+ # per rfc2616-sec14:
254
+ # answer with 406 if accept is given but types to not match any
255
+ # provided type
256
+ halt 406 if
257
+ (!url_format && !accepts.empty? && !matched_format) ||
258
+ (settings.respond_to?(:treat_format_as_accept) && settings.treat_format_as_accept && url_format && !matched_format)
259
+
260
+ if matched_format
261
+ @_content_type = url_format || accept_format || :html
262
+ content_type(@_content_type, :charset => 'utf-8')
263
+ end
264
+
265
+ matched_format
266
+ end
267
+ end
268
+
269
+ def process_path_for_parent_params(path, parent_params)
270
+ parent_prefix = parent_params.flatten.compact.uniq.map do |param|
271
+ map = (param.respond_to?(:map) && param.map ? param.map : param.to_s)
272
+ part = "#{map}/:#{param.to_s.singularize}_id/"
273
+ part = "(#{part})?" if param.respond_to?(:optional) && param.optional?
274
+ part
275
+ end
276
+
277
+ [parent_prefix, path].flatten.join("")
278
+ end
279
+
280
+ def process_path_for_provides(path, format_params)
281
+ path << "(.:format)?" unless path[-11, 11] == '(.:format)?'
282
+ end
283
+ end
284
+ end
285
+ end
286
+
287
+ if defined?(Padrino) && Padrino::VERSION < '0.12.0'
288
+ module Padrino
289
+ class Filter
290
+ def apply?(request)
291
+ detect = @args.any? do |arg|
292
+ case arg
293
+ when Symbol then request.route_obj && (request.route_obj.name == arg or request.route_obj.name == [@scoped_controller, arg].flatten.join(" ").to_sym)
294
+ else arg === request.path_info
295
+ end
296
+ end || @options.any? do |name, val|
297
+ case name
298
+ when :agent then val === request.user_agent
299
+ else val === request.send(name)
300
+ end
301
+ end
302
+ detect ^ !@mode
303
+ end
304
+ end
305
+ end
306
+ end
@@ -0,0 +1,63 @@
1
+ module Pendragon
2
+ module Padrino
3
+ module InstanceMethods
4
+ private
5
+
6
+ def invoke_route(route, params, options = {})
7
+ original_params, parent_layout, successful = @params.dup, @layout, false
8
+ captured_params = params[:captures].is_a?(Array) ? params.delete(:captures) :
9
+ params.values_at(*route.matcher.names.dup)
10
+
11
+ @_response_buffer = nil
12
+ @route = request.route_obj = route
13
+ @params.merge!(params) if params.is_a?(Hash)
14
+ @params.merge!(:captures => captured_params) if !captured_params.empty? && route.path.is_a?(Regexp)
15
+ @block_params = params
16
+
17
+ filter! :before if options[:first]
18
+
19
+ catch(:pass) do
20
+ begin
21
+ (route.before_filters - settings.filters[:before]).each{|block| instance_eval(&block) }
22
+ @layout = route.use_layout if route.use_layout
23
+ route.custom_conditions.each {|block| pass if block.bind(self).call == false }
24
+ halt_response = catch(:halt){ route_eval{ route.block[self, captured_params] }}
25
+ @_response_buffer = halt_response.is_a?(Array) ? halt_response.last : halt_response
26
+ successful = true
27
+ halt(halt_response)
28
+ ensure
29
+ (route.after_filters - settings.filters[:after]).each {|block| instance_eval(&block) } if successful
30
+ @layout, @params = parent_layout, original_params
31
+ end
32
+ end
33
+ end
34
+
35
+ def route!(base = settings, pass_block = nil)
36
+ Thread.current['padrino.instance'] = self
37
+ code, headers, routes = base.compiled_router.call(@request.env)
38
+
39
+ status(code)
40
+ if code == 200
41
+ routes.each_with_index do |(route, pendragon_params), index|
42
+ next if route.user_agent && !(route.user_agent =~ @request.user_agent)
43
+ invoke_route(route, pendragon_params, :first => index.zero?)
44
+ end
45
+ else
46
+ route_eval do
47
+ headers.each{|k, v| response[k] = v } unless headers.empty?
48
+ route_missing if code == 404
49
+ route_missing if allow = response['Allow'] and allow.include?(request.env['REQUEST_METHOD'])
50
+ end
51
+ end
52
+
53
+ if base.superclass.respond_to?(:router)
54
+ route!(base.superclass, pass_block)
55
+ return
56
+ end
57
+
58
+ route_eval(&pass_block) if pass_block
59
+ route_missing
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,50 @@
1
+ require 'pendragon/route'
2
+
3
+ module Pendragon
4
+ module Padrino
5
+ class Route < ::Pendragon::Route
6
+ attr_accessor :action, :cache, :cache_key, :cache_expires_in, :parent,
7
+ :use_layout, :controller, :user_agent, :path_for_generation
8
+
9
+ def before_filters(&block)
10
+ @_before_filters ||= []
11
+ @_before_filters << block if block_given?
12
+ @_before_filters
13
+ end
14
+
15
+ def after_filters(&block)
16
+ @_after_filters ||= []
17
+ @_after_filters << block if block_given?
18
+ @_after_filters
19
+ end
20
+
21
+ def custom_conditions(&block)
22
+ @_custom_conditions ||= []
23
+ @_custom_conditions << block if block_given?
24
+ @_custom_conditions
25
+ end
26
+
27
+ def call(app, *args)
28
+ @block.call(app, *args)
29
+ end
30
+
31
+ def request_methods
32
+ [verb.to_s.upcase]
33
+ end
34
+
35
+ def original_path
36
+ @path
37
+ end
38
+
39
+ def significant_variable_names
40
+ @significant_variable_names ||= if @path.is_a?(String)
41
+ @path.scan(/(^|[^\\])[:\*]([a-zA-Z0-9_]+)/).map{|p| p.last.to_sym}
42
+ elsif @path.is_a?(Regexp) and @path.respond_to?(:named_captures)
43
+ @path.named_captures.keys.map(&:to_sym)
44
+ else
45
+ []
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,45 @@
1
+ require 'pendragon' unless defined?(Pendragon::Router)
2
+
3
+ module Pendragon
4
+ module Padrino
5
+ class Router < Pendragon::Router
6
+ def add(verb, path, options = {}, &block)
7
+ route = Route.new(path, verb, options, &block)
8
+ route.path_for_generation = options[:path_for_generation] if options[:path_for_generation]
9
+ route.router = self
10
+ routes << route
11
+ route
12
+ end
13
+
14
+ def call(env)
15
+ request = Rack::Request.new(env)
16
+ raise BadRequest unless valid_verb?(request.request_method)
17
+ prepare! unless prepared?
18
+ [200, {}, recognize(request)]
19
+ rescue BadRequest, NotFound, MethodNotAllowed
20
+ $!.call
21
+ end
22
+
23
+ def path(name, *args)
24
+ params = args.delete_at(args.last.is_a?(Hash) ? -1 : 0) || {}
25
+ saved_args = args.dup
26
+ @routes.each do |route|
27
+ next unless route.options[:name] == name
28
+ matcher = route.matcher
29
+ if !args.empty? and matcher.mustermann?
30
+ matcher_names = matcher.names
31
+ params_for_expand = Hash[matcher_names.map{|matcher_name|
32
+ [matcher_name.to_sym, (params[matcher_name.to_sym] || args.shift)]
33
+ }]
34
+ params_for_expand.merge!(Hash[params.select{|k, v| !matcher_names.include?(name.to_sym) }])
35
+ args = saved_args.dup
36
+ else
37
+ params_for_expand = params.dup
38
+ end
39
+ return matcher.mustermann? ? matcher.expand(params_for_expand) : route.path_for_generation
40
+ end
41
+ raise InvalidRouteException
42
+ end
43
+ end
44
+ end
45
+ end