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.
@@ -7,9 +7,8 @@ module Pakyow
7
7
  include Helpers
8
8
 
9
9
  def path(name, data = nil)
10
- if route = get_named_route(name)
11
- data ? populate(route, data) : File.join('/', route[4])
12
- end
10
+ route = get_named_route(name)
11
+ data ? populate(route, data) : File.join('/', route[4])
13
12
  end
14
13
 
15
14
  def group(name)
@@ -29,9 +28,9 @@ module Pakyow
29
28
 
30
29
  def populate(route, data = {})
31
30
  vars = route[1]
32
-
31
+
33
32
  split_path = Request.split_url(route[4])
34
-
33
+
35
34
  vars.each {|v|
36
35
  split_path[v[:url_position]] = data.delete(v[:var])
37
36
  }
@@ -0,0 +1,76 @@
1
+ module Pakyow
2
+ module RouteMerger
3
+ private
4
+
5
+ def merge(route_eval)
6
+ merge_fns(route_eval.fns)
7
+ merge_routes(route_eval.routes)
8
+ merge_handlers(route_eval.handlers)
9
+ merge_lookup(route_eval.lookup)
10
+ merge_templates(route_eval.templates)
11
+ end
12
+
13
+ def merge_fns(fns)
14
+ @fns.merge!(fns)
15
+ end
16
+
17
+ def merge_routes(routes)
18
+ @routes[:get].concat(routes[:get])
19
+ @routes[:put].concat(routes[:put])
20
+ @routes[:patch].concat(routes[:patch])
21
+ @routes[:post].concat(routes[:post])
22
+ @routes[:delete].concat(routes[:delete])
23
+ end
24
+
25
+ def merge_handlers(handlers)
26
+ @handlers.concat(handlers)
27
+ end
28
+
29
+ def merge_lookup(lookup)
30
+ @lookup[:routes].merge!(lookup[:routes])
31
+ @lookup[:grouped].merge!(lookup[:grouped])
32
+ end
33
+
34
+ def merge_templates(templates)
35
+ @templates.merge!(templates)
36
+ end
37
+
38
+ #TODO should this accept one or two args?
39
+ def merge_hooks(h1, h2)
40
+ # normalize
41
+ h1 = normalize_hooks(h1)
42
+ h2 = normalize_hooks(h2)
43
+
44
+ # merge
45
+ h1[:before].concat(h2[:before])
46
+ h1[:after].concat(h2[:after])
47
+ h1[:around].concat(h2[:around])
48
+
49
+ return h1
50
+ end
51
+
52
+ def copy_hooks(hooks)
53
+ {
54
+ :before => (hooks[:before] || []).dup,
55
+ :after => (hooks[:after] || []).dup,
56
+ :around => (hooks[:around] || []).dup,
57
+ }
58
+ end
59
+
60
+ def normalize_hooks(hooks)
61
+ hooks ||= {}
62
+
63
+ [:before, :after, :around].each do |type|
64
+ # force array
65
+ hooks[type] = Array(hooks[type])
66
+
67
+ # lookup hook fns if not already a Proc
68
+ hooks[type] = hooks[type].map do |hook|
69
+ hook.is_a?(Symbol) ? fn(hook) : hook
70
+ end
71
+ end
72
+
73
+ return hooks
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,21 @@
1
+ require 'forwardable'
2
+
3
+ module Pakyow
4
+ # Include in a module to define route mixins
5
+ # that can be included into route sets.
6
+ module Routes
7
+ def self.included(base)
8
+ raise StandardError, "Pakyow::Routes is intended to be included only in other modules" if base.is_a?(Class)
9
+
10
+ base.extend(ClassMethods)
11
+ base.instance_variable_set(:@route_eval, RouteEval.new)
12
+ end
13
+
14
+ module ClassMethods
15
+ attr_reader :route_eval
16
+ extend Forwardable
17
+ def_delegators :@route_eval, :fn, :default, :get, :put, :post, :delete,
18
+ :handler, :group, :namespace, :template, :action, :expand
19
+ end
20
+ end
21
+ end
@@ -1,28 +1,35 @@
1
1
  module Pakyow
2
2
  class RouteSet
3
+ include RouteMerger
4
+
3
5
  attr_reader :routes, :lookup
4
6
 
5
7
  def initialize
6
- @routes = {:get => [], :post => [], :put => [], :delete => []}
8
+ @routes = {:get => [], :post => [], :put => [], :patch => [], :delete => []}
7
9
  @lookup = { :routes => {}, :grouped => {}}
8
10
  @handlers = []
9
11
  @fns = {}
12
+ @templates = {}
10
13
  end
11
14
 
12
15
  def eval(&block)
13
- evaluator = RouteEval.new
16
+ evaluator = RouteEval.with_defaults
14
17
  evaluator.eval(&block)
15
-
16
- @fns, @routes, @handlers, @lookup = evaluator.merge(@fns, @routes, @handlers, @lookup)
18
+ merge(evaluator)
17
19
  end
18
20
 
19
21
  # Returns a route tuple:
20
22
  # [regex, vars, name, fns, path]
21
23
  #
22
24
  def match(path, method)
23
- path = StringUtils.normalize_path(path)
25
+ path = Utils::String.normalize_path(path)
24
26
 
25
- @routes[method.to_sym].each{|r|
27
+ # want the request to still knows it's a head, but match as get
28
+ method = method.to_sym
29
+ method = :get if method == :head
30
+
31
+ @routes[method].each{|r|
32
+ #TODO can we do this without conditionals? fall-through?
26
33
  case r[0]
27
34
  when Regexp
28
35
  if data = r[0].match(path)
@@ -43,6 +50,7 @@ module Pakyow
43
50
  return h if h[0] == name_or_code || h[1] == name_or_code
44
51
  }
45
52
 
53
+ #TODO raise error
46
54
  nil
47
55
  end
48
56
 
@@ -53,11 +61,10 @@ module Pakyow
53
61
  if grouped_routes = @lookup[:grouped][group]
54
62
  return grouped_routes[name]
55
63
  else
56
- #TODO error
64
+ #TODO error (perhaps a set-specific exception rescued by router)
57
65
  end
58
66
  end
59
67
 
60
-
61
68
  # Name based fn lookup
62
69
  def fn(name)
63
70
  return @fns[name]
@@ -1,35 +1,33 @@
1
1
  module Pakyow
2
- class RouteTemplateDefaults
3
- def self.defaults
4
- lambda {
5
- template(:restful) {
6
- unnested_path = path.dup
7
- nested_path { |group, path|
8
- File.join(path, ":#{group}_id")
9
- }
10
-
11
- default fn(:default) if fn(:default)
12
-
13
- get '/new', :new, fn(:new) if fn(:new)
14
-
15
- # special case for show (view path is overridden)
16
- if show_fns = fn(:show)
17
- get '/:id', :show, Array(show_fns).unshift(
18
- lambda {
19
- #TODO would like to move this reference out of core
20
- presenter.view_path = File.join(unnested_path, 'show') if @presenter
21
- }
22
- )
23
- end
24
-
25
- post '/', :create, fn(:create) if fn(:create)
26
-
27
- get '/:id/edit', :edit, fn(:edit) if fn(:edit)
28
- put '/:id', :update, fn(:update) if fn(:update)
29
-
30
- delete '/:id', :delete, fn(:delete) if fn(:delete)
31
- }
32
- }
2
+ module Routes
3
+ module Restful
4
+ include Pakyow::Routes
5
+
6
+ template :restful do
7
+ resource_id = ":#{@group}_id"
8
+
9
+ nested_path { |path| File.join(path, resource_id) }
10
+ view_path = direct_path.gsub(/:[^\/]+/, '').split('/').reject { |p| p.empty? }.join('/')
11
+
12
+ fn :reset_view_path do
13
+ presenter.path = File.join(view_path, 'show') if @presenter
14
+ end
15
+
16
+ get :list, '/'
17
+ get :new, '/new'
18
+ get :show, "/#{resource_id}", before: [:reset_view_path]
19
+
20
+ post :create, '/'
21
+
22
+ get :edit, "/#{resource_id}/edit"
23
+ patch :update, "/#{resource_id}"
24
+ put :replace, "/#{resource_id}"
25
+ delete :delete, "/#{resource_id}"
26
+
27
+ group :collection
28
+ namespace :member, resource_id
29
+ end
30
+
33
31
  end
34
32
  end
35
33
  end
@@ -34,16 +34,16 @@ module Pakyow
34
34
  end
35
35
  }
36
36
 
37
- nil
37
+ raise MissingRoute, "Could not find route '#{name}'"
38
38
  end
39
39
 
40
40
  # Performs the initial routing for a request.
41
41
  #
42
- def perform(request, ctx = Pakyow.app, &after_match)
43
- fns = match(request)
42
+ def perform(context, app = Pakyow.app, &after_match)
43
+ fns = match(context.request)
44
44
  after_match.call if block_given?
45
45
 
46
- trampoline(fns, ctx)
46
+ trampoline(fns, app)
47
47
  end
48
48
 
49
49
  # Reroutes a request.
@@ -80,10 +80,15 @@ module Pakyow
80
80
 
81
81
  # Calls a defined fn
82
82
  #
83
- def fn(name)
83
+ def fn(name, app = nil)
84
84
  @sets.each { |set|
85
85
  if fn = set[1].fn(name)
86
- fn.call
86
+ if app.nil?
87
+ fn.call
88
+ else
89
+ app.instance_exec(&fn)
90
+ end
91
+
87
92
  break
88
93
  end
89
94
  }
@@ -101,7 +106,7 @@ module Pakyow
101
106
  # returns the list of route functions for that route.
102
107
  #
103
108
  def match(request)
104
- path = StringUtils.normalize_path(request.path)
109
+ path = Utils::String.normalize_path(request.path)
105
110
  method = request.method
106
111
 
107
112
  match, data = nil
@@ -116,7 +121,7 @@ module Pakyow
116
121
 
117
122
  # handle route params
118
123
  #TODO where to do this?
119
- request.params.merge!(HashUtils.strhash(self.data_from_path(path, data, match[1])))
124
+ request.params.merge!(Utils::Hash.strhash(self.data_from_path(path, data, match[1])))
120
125
 
121
126
  #TODO where to do this?
122
127
  request.route_path = match[4]
@@ -128,11 +133,11 @@ module Pakyow
128
133
  # Calls route functions and catches new functions as
129
134
  # they're thrown (e.g. by reroute).
130
135
  #
131
- def trampoline(fns, ctx)
136
+ def trampoline(fns, app)
132
137
  routed = false
133
138
  until fns.empty?
134
139
  fns = catch(:fns) {
135
- self.call_fns(fns, ctx)
140
+ self.call_fns(fns, app)
136
141
 
137
142
  # Getting here means that call() returned normally (not via a throw)
138
143
  :fall_through
@@ -1,12 +1,13 @@
1
1
  module Pakyow
2
+ module Utils
2
3
 
3
4
  # Utility methods for directories and files.
4
- class DirUtils
5
+ class Dir
5
6
 
6
7
  # visit dir, then all files in dir, then walk_dir each directory in dir
7
8
  def self.walk_dir(dir, &block)
8
9
  yield dir
9
- all = Dir.entries(dir)
10
+ all = ::Dir.entries(dir)
10
11
  partition = all.partition{|e| File.file?("#{dir}/#{e}")}
11
12
  files = partition[0]
12
13
  dirs = partition[1]
@@ -16,7 +17,7 @@ module Pakyow
16
17
 
17
18
  def self.print_dir(dir)
18
19
  puts "/#{dir}"
19
- DirUtils.walk_dir(dir) {|full_path|
20
+ Utils::Dir.walk_dir(dir) {|full_path|
20
21
  path = full_path.gsub(Regexp.new("#{dir}\/?"), '')
21
22
  next if path.empty?
22
23
 
@@ -33,5 +34,7 @@ module Pakyow
33
34
  def self.dir_within_dir?(dir1, dir2)
34
35
  (dir1.split('/') - dir2.split('/')).empty?
35
36
  end
37
+ end
38
+
36
39
  end
37
40
  end
@@ -1,41 +1,44 @@
1
1
  module Pakyow
2
+ module Utils
2
3
 
3
- # Utility methods for hashes.
4
- class HashUtils
5
- # Creates an indifferent hash. This means that when indifferentized, this hash:
6
- # { 'foo' => 'bar' }
7
- #
8
- # Can be accessed like this:
9
- # { :foo => 'bar' }
10
- #
11
- def self.strhash(hash)
12
- indifferentize(hash)
13
- end
4
+ # Utility methods for hashes.
5
+ class Hash
6
+ # Creates an indifferent hash. This means that when indifferentized, this hash:
7
+ # { 'foo' => 'bar' }
8
+ #
9
+ # Can be accessed like this:
10
+ # { :foo => 'bar' }
11
+ #
12
+ def self.strhash(hash)
13
+ indifferentize(hash)
14
+ end
14
15
 
15
- # Converts keys to symbols.
16
- def self.symbolize_keys(hash)
17
- Hash[hash.map{|(k,v)| [k.to_sym,v]}]
18
- end
16
+ # Converts keys to symbols.
17
+ def self.symbolize_keys(hash)
18
+ ::Hash[hash.map{|(k,v)| [k.to_sym,v]}]
19
+ end
19
20
 
20
- # Converts keys/values to symbols.
21
- def self.symbolize(hash)
22
- Hash[hash.map{|(k,v)| [k.to_sym,v.to_sym]}]
23
- end
21
+ # Converts keys/values to symbols.
22
+ def self.symbolize(hash)
23
+ ::Hash[hash.map{|(k,v)| [k.to_sym,v.to_sym]}]
24
+ end
25
+
26
+ protected
24
27
 
25
- protected
28
+ # (see {strhash})
29
+ def self.indifferentize(hash)
30
+ hash.each_pair do |key, value|
31
+ hash[key] = indifferentize(value) if value.is_a? ::Hash
32
+ end
26
33
 
27
- # (see {strhash})
28
- def self.indifferentize(hash)
29
- hash.each_pair do |key, value|
30
- hash[key] = indifferentize(value) if value.is_a? Hash
34
+ indifferent_hash.merge(hash)
31
35
  end
32
36
 
33
- indifferent_hash.merge(hash)
37
+ # (see {strhash})
38
+ def self.indifferent_hash
39
+ ::Hash.new { |hash,key| hash[key.to_s] if Symbol === key }
40
+ end
34
41
  end
35
42
 
36
- # (see {strhash})
37
- def self.indifferent_hash
38
- Hash.new { |hash,key| hash[key.to_s] if Symbol === key }
39
- end
40
43
  end
41
44
  end
@@ -1,37 +1,40 @@
1
1
  module Pakyow
2
+ module Utils
2
3
 
3
- # Utility methods for strings.
4
- class StringUtils
4
+ # Utility methods for strings.
5
+ class String
5
6
 
6
- # split . seperated string at the last .
7
- def self.split_at_last_dot(s)
8
- split_index = s.rindex('.')
9
- return s,nil unless split_index
10
- left = s[0,split_index]
11
- right = s[split_index+1,s.length-(split_index+1)]
12
- return left,right
13
- end
7
+ # split . seperated string at the last .
8
+ def self.split_at_last_dot(s)
9
+ split_index = s.rindex('.')
10
+ return s,nil unless split_index
11
+ left = s[0,split_index]
12
+ right = s[split_index+1,s.length-(split_index+1)]
13
+ return left,right
14
+ end
14
15
 
15
- def self.remove_route_vars(route_spec)
16
- return unless route_spec
17
- arr = route_spec.split('/')
18
- new_arr = []
19
- arr.each {|e| new_arr << e unless e[0,1] == ':'}
20
- ret = new_arr.join('/')
21
- return '/' if ret == ''
22
- return ret
23
- end
16
+ def self.remove_route_vars(route_spec)
17
+ return unless route_spec
18
+ arr = route_spec.split('/')
19
+ new_arr = []
20
+ arr.each {|e| new_arr << e unless e[0,1] == ':'}
21
+ ret = new_arr.join('/')
22
+ return '/' if ret == ''
23
+ return ret
24
+ end
24
25
 
25
- def self.parse_path_from_caller(caller)
26
- caller.match(/^(.+)(:?:\d+(:?:in `.+')?$)/)[1]
27
- end
26
+ def self.parse_path_from_caller(caller)
27
+ caller.match(/^(.+)(:?:\d+(:?:in `.+')?$)/)[1]
28
+ end
29
+
30
+ def self.normalize_path(path)
31
+ return path if path.is_a?(Regexp)
28
32
 
29
- def self.normalize_path(path)
30
- return path if path.is_a?(Regexp)
33
+ path = path[1, path.length - 1] if path[0, 1] == '/'
34
+ path = path[0, path.length - 1] if path[path.length - 1, 1] == '/'
35
+ path
36
+ end
31
37
 
32
- path = path[1, path.length - 1] if path[0, 1] == '/'
33
- path = path[0, path.length - 1] if path[path.length - 1, 1] == '/'
34
- path
35
38
  end
36
39
 
37
40
  end