pakyow-core 0.8.rc4 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/pakyow-core/CHANGES +4 -0
- data/pakyow-core/lib/core/app.rb +51 -50
- data/pakyow-core/lib/core/app_context.rb +10 -0
- data/pakyow-core/lib/core/base.rb +4 -1
- data/pakyow-core/lib/core/errors.rb +13 -0
- data/pakyow-core/lib/core/helpers.rb +6 -3
- data/pakyow-core/lib/core/loader.rb +5 -5
- data/pakyow-core/lib/core/middleware/static.rb +3 -2
- data/pakyow-core/lib/core/request.rb +8 -9
- data/pakyow-core/lib/core/response.rb +12 -0
- data/pakyow-core/lib/core/route_eval.rb +252 -159
- data/pakyow-core/lib/core/route_lookup.rb +4 -5
- data/pakyow-core/lib/core/route_merger.rb +76 -0
- data/pakyow-core/lib/core/route_module.rb +21 -0
- data/pakyow-core/lib/core/route_set.rb +15 -8
- data/pakyow-core/lib/core/route_template_defaults.rb +29 -31
- data/pakyow-core/lib/core/router.rb +15 -10
- data/pakyow-core/lib/utils/dir.rb +6 -3
- data/pakyow-core/lib/utils/hash.rb +32 -29
- data/pakyow-core/lib/utils/string.rb +30 -27
- metadata +15 -12
- data/pakyow-core/lib/core/exceptions.rb +0 -3
@@ -2,5 +2,17 @@ module Pakyow
|
|
2
2
|
|
3
3
|
# The Response object.
|
4
4
|
class Response < Rack::Response
|
5
|
+
attr_reader :format
|
6
|
+
|
7
|
+
def initialize(*args)
|
8
|
+
super
|
9
|
+
|
10
|
+
self["Content-Type"] ||= 'text/html'
|
11
|
+
end
|
12
|
+
|
13
|
+
def format=(format)
|
14
|
+
@format = format
|
15
|
+
self["Content-Type"] = Rack::Mime.mime_type(".#{format}")
|
16
|
+
end
|
5
17
|
end
|
6
18
|
end
|
@@ -1,84 +1,65 @@
|
|
1
1
|
module Pakyow
|
2
2
|
class RouteEval
|
3
|
-
|
4
|
-
|
5
|
-
def initialize(path = '/', hooks = { :before => [], :after => []}, fns = {}, group_name = nil, namespace = false)
|
6
|
-
@path = path
|
7
|
-
@scope = {:path => path, :hooks => hooks, :group_name => group_name, :namespace => namespace}
|
8
|
-
@routes = {:get => [], :post => [], :put => [], :delete => []}
|
9
|
-
@lookup = {:routes => {}, :grouped => {}}
|
10
|
-
@fns = fns
|
11
|
-
@groups = {}
|
12
|
-
@templates = {}
|
13
|
-
@handlers = []
|
14
|
-
|
15
|
-
eval(&RouteTemplateDefaults.defaults)
|
16
|
-
end
|
3
|
+
include RouteMerger
|
17
4
|
|
18
|
-
|
19
|
-
# if we're evaling a template, need to push
|
20
|
-
# member routes to the end (they're always
|
21
|
-
# created first, but need to be the last out)
|
22
|
-
if template
|
23
|
-
@member_routes = @routes
|
24
|
-
@routes = {:get => [], :post => [], :put => [], :delete => []}
|
25
|
-
end
|
5
|
+
attr_reader :path, :fns, :hooks, :group, :routes, :handlers, :lookup, :templates
|
26
6
|
|
27
|
-
|
7
|
+
HTTP_METHODS = [:get, :post, :put, :patch, :delete]
|
8
|
+
DEFAULT_MIXINS = ['Restful']
|
28
9
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
end
|
10
|
+
class << self
|
11
|
+
def with_defaults(*args)
|
12
|
+
instance = self.new(*args)
|
33
13
|
|
34
|
-
|
35
|
-
|
36
|
-
merge_routes(routes, @routes)
|
37
|
-
handlers.concat(@handlers)
|
38
|
-
# lookup.merge!(@lookup)
|
39
|
-
fns.merge!(@fns)
|
14
|
+
# Mixin defaults
|
15
|
+
DEFAULT_MIXINS.each { |mixin| instance.include(Pakyow::Routes.const_get(mixin)) }
|
40
16
|
|
41
|
-
|
17
|
+
return instance
|
18
|
+
end
|
42
19
|
|
43
|
-
|
44
|
-
|
20
|
+
def from_scope(route_eval, overrides = {})
|
21
|
+
args = [:path, :fns, :hooks, :templates, :group].inject([]) do |acc, arg|
|
22
|
+
acc << (overrides.fetch(arg) { route_eval.send(arg) })
|
23
|
+
end
|
45
24
|
|
46
|
-
|
47
|
-
|
48
|
-
def fn(name, &block)
|
49
|
-
@fns[name] = block and return if block
|
50
|
-
@fns[name]
|
25
|
+
instance = self.new(*args)
|
26
|
+
end
|
51
27
|
end
|
52
28
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
fns = block_given? ? [block] : [fn]
|
61
|
-
@fns[method] = build_fns(fns, hooks)
|
62
|
-
end
|
29
|
+
def initialize(path = '/', fns = nil, hooks = nil, templates = nil, group = nil)
|
30
|
+
@path = path
|
31
|
+
@fns = fns || {}
|
32
|
+
@routes = HTTP_METHODS.inject({}) { |acc, m| acc[m] = []; acc }
|
33
|
+
@hooks = hooks || { before: [], after: [] }
|
34
|
+
@templates = templates || {}
|
35
|
+
@group = group
|
63
36
|
|
64
|
-
|
65
|
-
|
37
|
+
@lookup = { routes: {}, grouped: {} }
|
38
|
+
@handlers = []
|
66
39
|
end
|
67
40
|
|
68
|
-
|
69
|
-
|
41
|
+
# Path for evals within this eval
|
42
|
+
#
|
43
|
+
def descendent_path
|
44
|
+
@descendent_path || @path
|
70
45
|
end
|
71
46
|
|
72
|
-
def
|
73
|
-
|
47
|
+
def include(route_module)
|
48
|
+
merge(route_module.route_eval)
|
74
49
|
end
|
75
50
|
|
76
|
-
def
|
77
|
-
|
51
|
+
def eval(&block)
|
52
|
+
instance_exec(&block)
|
78
53
|
end
|
79
54
|
|
80
|
-
|
81
|
-
|
55
|
+
# Creates or retreives a named route function. When retrieving,
|
56
|
+
#
|
57
|
+
def fn(name, &block)
|
58
|
+
if block_given?
|
59
|
+
@fns[name] = block
|
60
|
+
else
|
61
|
+
@fns[name]
|
62
|
+
end
|
82
63
|
end
|
83
64
|
|
84
65
|
# Creates a handler.
|
@@ -98,81 +79,68 @@ module Pakyow
|
|
98
79
|
def group(*args, &block)
|
99
80
|
name, hooks = self.class.parse_group_args(args)
|
100
81
|
|
101
|
-
evaluator = RouteEval.
|
82
|
+
evaluator = RouteEval.from_scope(self, path: descendent_path, group: name, hooks: hooks)
|
102
83
|
evaluator.eval(&block)
|
103
84
|
|
104
|
-
|
85
|
+
merge(evaluator)
|
105
86
|
end
|
106
87
|
|
107
88
|
def namespace(*args, &block)
|
108
89
|
path, name, hooks = self.class.parse_namespace_args(args)
|
109
90
|
|
110
|
-
|
111
|
-
hooks = name if name.is_a?(Hash)
|
112
|
-
|
113
|
-
evaluator = RouteEval.new(File.join(@scope[:path], path), merge_hooks(hooks, @scope[:hooks]), @fns, name, true)
|
91
|
+
evaluator = RouteEval.from_scope(self, path: File.join(descendent_path, path), group: name, hooks: hooks)
|
114
92
|
evaluator.eval(&block)
|
115
93
|
|
116
|
-
|
94
|
+
merge(evaluator)
|
117
95
|
end
|
118
96
|
|
119
97
|
def template(*args, &block)
|
120
98
|
name, hooks = self.class.parse_template_args(args)
|
99
|
+
|
121
100
|
@templates[name] = [hooks, block]
|
122
101
|
end
|
123
102
|
|
124
|
-
def expand(t_name, g_name, *args, &block)
|
103
|
+
def expand(t_name, g_name = nil, *args, &block)
|
125
104
|
path, hooks = self.class.parse_expansion_args(args)
|
105
|
+
path ||= ''
|
126
106
|
|
127
107
|
template = @templates[t_name]
|
128
108
|
|
129
|
-
evaluator =
|
109
|
+
evaluator = RouteExpansionEval.from_scope(self, path: File.join(descendent_path, path), group: g_name, hooks: hooks)
|
110
|
+
evaluator.direct_path = path
|
111
|
+
evaluator.template = template
|
130
112
|
evaluator.eval(&block)
|
131
|
-
evaluator.eval(true, &template[1])
|
132
113
|
|
133
|
-
|
114
|
+
merge(evaluator)
|
134
115
|
end
|
135
116
|
|
136
|
-
|
137
|
-
|
138
|
-
def merge_hooks(h1, h2)
|
139
|
-
# normalize
|
140
|
-
h1 = normalize_hooks(h1)
|
141
|
-
h2 = normalize_hooks(h2)
|
142
|
-
|
143
|
-
# merge
|
144
|
-
h1[:before].concat(h2[:before])
|
145
|
-
h1[:after].concat(h2[:after])
|
146
|
-
h1[:around].concat(h2[:around])
|
147
|
-
|
148
|
-
return h1
|
117
|
+
def default(*args, &block)
|
118
|
+
build_route(:get, '/', :default, *args, &block)
|
149
119
|
end
|
150
120
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
r1[:delete].concat(r2[:delete])
|
156
|
-
|
157
|
-
return r1
|
121
|
+
HTTP_METHODS.each do |method|
|
122
|
+
define_method method do |*args, &block|
|
123
|
+
build_route(method, *args, &block)
|
124
|
+
end
|
158
125
|
end
|
159
126
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
127
|
+
# For the expansion of templates
|
128
|
+
def method_missing(method, *args, &block)
|
129
|
+
if template_defined?(method)
|
130
|
+
expand(method, *args, &block)
|
131
|
+
else
|
132
|
+
super
|
133
|
+
# action(method, *args, &block)
|
134
|
+
end
|
165
135
|
end
|
166
136
|
|
167
|
-
def
|
168
|
-
|
169
|
-
:before => (hooks[:before] || []).dup,
|
170
|
-
:after => (hooks[:after] || []).dup,
|
171
|
-
:around => (hooks[:around] || []).dup,
|
172
|
-
}
|
137
|
+
def template_defined?(template)
|
138
|
+
!@templates[template].nil?
|
173
139
|
end
|
174
140
|
|
175
|
-
|
141
|
+
protected
|
142
|
+
|
143
|
+
def build_route(method, *args, &block)
|
176
144
|
path, name, fns, hooks = self.class.parse_route_args(args)
|
177
145
|
|
178
146
|
fns ||= []
|
@@ -180,7 +148,7 @@ module Pakyow
|
|
180
148
|
fns << block if block_given?
|
181
149
|
|
182
150
|
# merge route hooks with scoped hooks
|
183
|
-
hooks = merge_hooks(hooks || {}, @
|
151
|
+
hooks = merge_hooks(hooks || {}, @hooks)
|
184
152
|
|
185
153
|
# build the final list of fns
|
186
154
|
fns = build_fns(fns, hooks)
|
@@ -190,28 +158,26 @@ module Pakyow
|
|
190
158
|
vars = []
|
191
159
|
else
|
192
160
|
# prepend scope path if we're in a scope
|
193
|
-
path = File.join(@
|
194
|
-
path =
|
161
|
+
path = File.join(@path, path)
|
162
|
+
path = Utils::String.normalize_path(path)
|
195
163
|
|
196
164
|
# get regex and vars for path
|
197
165
|
regex, vars = build_route_matcher(path)
|
198
166
|
end
|
199
167
|
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
@routes[method] << route
|
168
|
+
register_route([regex, vars, name, fns, path, method])
|
169
|
+
end
|
204
170
|
|
205
|
-
|
206
|
-
|
207
|
-
unless namespace?
|
208
|
-
@lookup[:routes][name] = route
|
209
|
-
end
|
171
|
+
def register_route(route)
|
172
|
+
@routes[route[5]] << route
|
210
173
|
|
211
|
-
# add to grouped lookup, if we're in a group
|
212
174
|
if group?
|
213
|
-
(@lookup[:grouped][@
|
175
|
+
bucket = (@lookup[:grouped][@group] ||= {})
|
176
|
+
else
|
177
|
+
bucket = @lookup[:routes]
|
214
178
|
end
|
179
|
+
|
180
|
+
bucket[route[2]] = route
|
215
181
|
end
|
216
182
|
|
217
183
|
def build_route_matcher(path)
|
@@ -236,30 +202,7 @@ module Pakyow
|
|
236
202
|
end
|
237
203
|
|
238
204
|
def group?
|
239
|
-
!@
|
240
|
-
end
|
241
|
-
|
242
|
-
def namespace?
|
243
|
-
@scope[:namespace]
|
244
|
-
end
|
245
|
-
|
246
|
-
# yields current path to the block for modification,
|
247
|
-
# then updates paths for member routes
|
248
|
-
def nested_path
|
249
|
-
new_path = yield(@scope[:group_name], @path)
|
250
|
-
|
251
|
-
# update paths of member routes
|
252
|
-
@member_routes.each {|type,routes|
|
253
|
-
routes.each { |route|
|
254
|
-
path = StringUtils.normalize_path(File.join(new_path, route[4].gsub(/^#{StringUtils.normalize_path(@path)}/, '')))
|
255
|
-
regex, vars = build_route_matcher(path)
|
256
|
-
route[0] = regex
|
257
|
-
route[1] = vars
|
258
|
-
route[4] = path
|
259
|
-
}
|
260
|
-
}
|
261
|
-
|
262
|
-
@path = new_path
|
205
|
+
!@group.nil?
|
263
206
|
end
|
264
207
|
|
265
208
|
def build_fns(main_fns, hooks)
|
@@ -273,22 +216,6 @@ module Pakyow
|
|
273
216
|
fns
|
274
217
|
end
|
275
218
|
|
276
|
-
def normalize_hooks(hooks)
|
277
|
-
hooks ||= {}
|
278
|
-
|
279
|
-
[:before, :after, :around].each do |type|
|
280
|
-
# force array
|
281
|
-
hooks[type] = Array(hooks[type])
|
282
|
-
|
283
|
-
# lookup hook fns if not already a Proc
|
284
|
-
hooks[type] = hooks[type].map do |hook|
|
285
|
-
hook.is_a?(Symbol) ? fn(hook) : hook
|
286
|
-
end
|
287
|
-
end
|
288
|
-
|
289
|
-
return hooks
|
290
|
-
end
|
291
|
-
|
292
219
|
class << self
|
293
220
|
def parse_route_args(args)
|
294
221
|
ret = []
|
@@ -373,7 +300,82 @@ module Pakyow
|
|
373
300
|
}
|
374
301
|
ret
|
375
302
|
end
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
class RouteExpansionEval < RouteEval
|
307
|
+
attr_writer :direct_path
|
376
308
|
|
309
|
+
def eval(&block)
|
310
|
+
@template_eval = RouteTemplateEval.from_scope(self, path: path, group: @group, hooks: @hooks)
|
311
|
+
@template_eval.direct_path = @direct_path
|
312
|
+
@template_eval.eval(&@template_block)
|
313
|
+
|
314
|
+
@path = @template_eval.routes_path
|
315
|
+
|
316
|
+
super
|
317
|
+
end
|
318
|
+
|
319
|
+
def template=(template)
|
320
|
+
@template_block = template[1]
|
321
|
+
|
322
|
+
@hooks = merge_hooks(@hooks, template[0])
|
323
|
+
end
|
324
|
+
|
325
|
+
def action(method, *args, &block)
|
326
|
+
fn, hooks = self.class.parse_action_args(args)
|
327
|
+
fn = block if block_given?
|
328
|
+
|
329
|
+
# get route info from template
|
330
|
+
route = @template_eval.route_for_action(method)
|
331
|
+
|
332
|
+
all_fns = route[3]
|
333
|
+
all_fns[:fns].unshift(fn) if fn
|
334
|
+
|
335
|
+
hooks = merge_hooks(hooks, all_fns[:hooks])
|
336
|
+
route[3] = build_fns(all_fns[:fns], hooks)
|
337
|
+
|
338
|
+
register_route(route)
|
339
|
+
end
|
340
|
+
|
341
|
+
def action_group(*args, &block)
|
342
|
+
name, hooks = self.class.parse_action_group_args(args)
|
343
|
+
group = @template_eval.group_named(name)
|
344
|
+
|
345
|
+
hooks = merge_hooks(hooks, group[0])
|
346
|
+
group(name, hooks, &block)
|
347
|
+
end
|
348
|
+
|
349
|
+
def action_namespace(*args, &block)
|
350
|
+
name, hooks = self.class.parse_action_namespace_args(args)
|
351
|
+
namespace = @template_eval.namespace_named(name)
|
352
|
+
|
353
|
+
hooks = merge_hooks(hooks, namespace[1])
|
354
|
+
namespace(name, namespace[0], hooks, &block)
|
355
|
+
end
|
356
|
+
|
357
|
+
def method_missing(method, *args, &block)
|
358
|
+
if @template_eval.has_action?(method)
|
359
|
+
action(method, *args, &block)
|
360
|
+
elsif @template_eval.has_namespace?(method)
|
361
|
+
action_namespace(method, *args, &block)
|
362
|
+
elsif @template_eval.has_group?(method)
|
363
|
+
action_group(method, *args, &block)
|
364
|
+
else
|
365
|
+
super
|
366
|
+
end
|
367
|
+
rescue NoMethodError
|
368
|
+
raise UnknownTemplatePart, "No action, namespace, or group named '#{method}'"
|
369
|
+
end
|
370
|
+
|
371
|
+
def expand(*args, &block)
|
372
|
+
args[2] = File.join(@template_eval.nested_path.gsub(@path, ''), args[2])
|
373
|
+
super(*args, &block)
|
374
|
+
end
|
375
|
+
|
376
|
+
private
|
377
|
+
|
378
|
+
class << self
|
377
379
|
def parse_action_args(args)
|
378
380
|
ret = []
|
379
381
|
args.each { |arg|
|
@@ -385,6 +387,97 @@ module Pakyow
|
|
385
387
|
}
|
386
388
|
ret
|
387
389
|
end
|
388
|
-
|
390
|
+
|
391
|
+
def parse_action_namespace_args(args)
|
392
|
+
ret = []
|
393
|
+
args.each { |arg|
|
394
|
+
if arg.is_a?(Hash) # we have hooks
|
395
|
+
ret[1] = arg
|
396
|
+
elsif arg.is_a?(Symbol) # we have a name
|
397
|
+
ret[0] = arg
|
398
|
+
end
|
399
|
+
}
|
400
|
+
ret
|
401
|
+
end
|
402
|
+
|
403
|
+
def parse_action_group_args(args)
|
404
|
+
ret = []
|
405
|
+
args.each { |arg|
|
406
|
+
if arg.is_a?(Hash) # we have hooks
|
407
|
+
ret[1] = arg
|
408
|
+
elsif !arg.nil? # we have a name
|
409
|
+
ret[0] = arg
|
410
|
+
end
|
411
|
+
}
|
412
|
+
ret
|
413
|
+
end
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
class RouteTemplateEval < RouteEval
|
418
|
+
attr_accessor :direct_path
|
419
|
+
|
420
|
+
def initialize(*args)
|
421
|
+
super
|
422
|
+
|
423
|
+
@groups = {}
|
424
|
+
@namespaces = {}
|
425
|
+
|
426
|
+
@routes_path = path
|
427
|
+
@nested_path = path
|
428
|
+
end
|
429
|
+
|
430
|
+
def has_action?(name)
|
431
|
+
!route_for_action(name).nil?
|
432
|
+
end
|
433
|
+
|
434
|
+
def has_group?(name)
|
435
|
+
!group_named(name).nil?
|
436
|
+
end
|
437
|
+
|
438
|
+
def has_namespace?(name)
|
439
|
+
!namespace_named(name).nil?
|
440
|
+
end
|
441
|
+
|
442
|
+
def route_for_action(name)
|
443
|
+
lookup.fetch(:grouped, {}).fetch(@group, {})[name]
|
444
|
+
end
|
445
|
+
|
446
|
+
def namespace_named(name)
|
447
|
+
@namespaces[name]
|
448
|
+
end
|
449
|
+
|
450
|
+
def group_named(name)
|
451
|
+
@groups[name]
|
452
|
+
end
|
453
|
+
|
454
|
+
def build_fns(fns, hooks)
|
455
|
+
{
|
456
|
+
fns: fns,
|
457
|
+
hooks: hooks,
|
458
|
+
}
|
459
|
+
end
|
460
|
+
|
461
|
+
def namespace(*args)
|
462
|
+
path, name, hooks = self.class.parse_namespace_args(args)
|
463
|
+
@namespaces[name] = [path, hooks]
|
464
|
+
end
|
465
|
+
|
466
|
+
def group(*args)
|
467
|
+
name, hooks = self.class.parse_group_args(args)
|
468
|
+
@groups[name] = [hooks]
|
469
|
+
end
|
470
|
+
|
471
|
+
def routes_path(&block)
|
472
|
+
return @routes_path unless block_given?
|
473
|
+
@routes_path = yield(@routes_path)
|
474
|
+
@path = @routes_path
|
475
|
+
end
|
476
|
+
|
477
|
+
def nested_path(&block)
|
478
|
+
return @nested_path unless block_given?
|
479
|
+
@nested_path = yield(@nested_path)
|
480
|
+
end
|
389
481
|
end
|
390
482
|
end
|
483
|
+
|