pendragon 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.travis.yml +13 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +64 -0
- data/README.md +159 -0
- data/Rakefile +19 -0
- data/config.ru +9 -0
- data/lib/pendragon.rb +36 -0
- data/lib/pendragon/compile_helpers.rb +41 -0
- data/lib/pendragon/configuration.rb +25 -0
- data/lib/pendragon/error_handler.rb +43 -0
- data/lib/pendragon/matcher.rb +78 -0
- data/lib/pendragon/padrino.rb +15 -0
- data/lib/pendragon/padrino/ext/class_methods.rb +306 -0
- data/lib/pendragon/padrino/ext/instance_methods.rb +63 -0
- data/lib/pendragon/padrino/route.rb +50 -0
- data/lib/pendragon/padrino/router.rb +45 -0
- data/lib/pendragon/route.rb +60 -0
- data/lib/pendragon/router.rb +188 -0
- data/lib/pendragon/version.rb +4 -0
- data/pendragon.gemspec +20 -0
- data/test/compile_helper.rb +5 -0
- data/test/helper.rb +87 -0
- data/test/padrino_test.rb +1942 -0
- data/test/pendragon_test.rb +139 -0
- metadata +168 -0
@@ -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
|