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.
@@ -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
- attr_reader :path
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
- def eval(template = false, &block)
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
- instance_exec(&block)
7
+ HTTP_METHODS = [:get, :post, :put, :patch, :delete]
8
+ DEFAULT_MIXINS = ['Restful']
28
9
 
29
- if template
30
- merge_routes(@routes, @member_routes)
31
- end
32
- end
10
+ class << self
11
+ def with_defaults(*args)
12
+ instance = self.new(*args)
33
13
 
34
- def merge(fns, routes, handlers, lookup)
35
- # routes.merge!(@routes)
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
- merge_lookup(lookup, @lookup)
17
+ return instance
18
+ end
42
19
 
43
- return fns, routes, handlers, lookup
44
- end
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
- # Creates or retreives a named route function. When retrieving,
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
- # def action(name, &block)
54
- # @fns[name] = block and return if block
55
- # @fns[name]
56
- # end
57
-
58
- def action(method, *args, &block)
59
- fn, hooks = self.class.parse_action_args(args)
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
- def default(*args, &block)
65
- register_route(:get, '/', :default, *args, &block)
37
+ @lookup = { routes: {}, grouped: {} }
38
+ @handlers = []
66
39
  end
67
40
 
68
- def get(*args, &block)
69
- register_route(:get, *args, &block)
41
+ # Path for evals within this eval
42
+ #
43
+ def descendent_path
44
+ @descendent_path || @path
70
45
  end
71
46
 
72
- def put(*args, &block)
73
- register_route(:put, *args, &block)
47
+ def include(route_module)
48
+ merge(route_module.route_eval)
74
49
  end
75
50
 
76
- def post(*args, &block)
77
- register_route(:post, *args, &block)
51
+ def eval(&block)
52
+ instance_exec(&block)
78
53
  end
79
54
 
80
- def delete(*args, &block)
81
- register_route(:delete, *args, &block)
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.new(@scope[:path], merge_hooks(hooks, @scope[:hooks]), @fns, name)
82
+ evaluator = RouteEval.from_scope(self, path: descendent_path, group: name, hooks: hooks)
102
83
  evaluator.eval(&block)
103
84
 
104
- @fns, @routes, @handlers, @lookup = evaluator.merge(@fns, @routes, @handlers, @lookup)
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
- #TODO shouldn't this be in parse_namespace_args?
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
- @fns, @routes, @handlers, @lookup = evaluator.merge(@fns, @routes, @handlers, @lookup)
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 = RouteEval.new(File.join(@scope[:path], path), merge_hooks(merge_hooks(hooks, @scope[:hooks]), template[0]), @fns, g_name, true)
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
- @fns, @routes, @handlers, @lookup = evaluator.merge(@fns, @routes, @handlers, @lookup)
114
+ merge(evaluator)
134
115
  end
135
116
 
136
- protected
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
- def merge_routes(r1, r2)
152
- r1[:get].concat(r2[:get])
153
- r1[:put].concat(r2[:put])
154
- r1[:post].concat(r2[:post])
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
- def merge_lookup(l1, l2)
161
- l1[:routes].merge!(l2[:routes])
162
- l1[:grouped].merge!(l2[:grouped])
163
-
164
- return l1
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 copy_hooks(hooks)
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
- def register_route(method, *args, &block)
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 || {}, @scope[: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(@scope[:path], path)
194
- path = StringUtils.normalize_path(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
- # create the route tuple
201
- route = [regex, vars, name, fns, path]
202
-
203
- @routes[method] << route
168
+ register_route([regex, vars, name, fns, path, method])
169
+ end
204
170
 
205
- # add route to lookup, unless it's namespaced (because
206
- # then it can only be accessed through the grouping)
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][@scope[:group_name]] ||= {})[name] = route
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
- !@scope[:group_name].nil?
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
- end
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
+