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.
- 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
@@ -7,9 +7,8 @@ module Pakyow
|
|
7
7
|
include Helpers
|
8
8
|
|
9
9
|
def path(name, data = nil)
|
10
|
-
|
11
|
-
|
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.
|
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 =
|
25
|
+
path = Utils::String.normalize_path(path)
|
24
26
|
|
25
|
-
|
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
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
-
|
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(
|
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,
|
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
|
-
|
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 =
|
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!(
|
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,
|
136
|
+
def trampoline(fns, app)
|
132
137
|
routed = false
|
133
138
|
until fns.empty?
|
134
139
|
fns = catch(:fns) {
|
135
|
-
self.call_fns(fns,
|
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
|
-
|
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
|
-
|
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
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
4
|
-
|
4
|
+
# Utility methods for strings.
|
5
|
+
class String
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
30
|
-
|
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
|