pakyow-core 0.8.rc4 → 0.8.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.
@@ -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
+