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.
- 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
|