usher 0.4.8 → 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +12 -1
- data/Rakefile +5 -29
- data/VERSION.yml +2 -2
- data/lib/usher.rb +126 -37
- data/lib/usher/grapher.rb +5 -4
- data/lib/usher/interface.rb +12 -5
- data/lib/usher/interface/email_interface.rb +1 -1
- data/lib/usher/interface/rack_interface.rb +31 -13
- data/lib/usher/interface/rails2_2_interface.rb +3 -4
- data/lib/usher/interface/rails2_3_interface.rb +3 -4
- data/lib/usher/interface/rails3_interface.rb +57 -0
- data/lib/usher/node.rb +121 -73
- data/lib/usher/route.rb +45 -11
- data/lib/usher/route/path.rb +50 -11
- data/lib/usher/route/util.rb +65 -0
- data/lib/usher/route/variable.rb +22 -11
- data/lib/usher/splitter.rb +4 -141
- data/lib/usher/util.rb +6 -0
- data/lib/usher/util/generate.rb +129 -0
- data/lib/usher/util/parser.rb +145 -0
- data/spec/private/email/recognize_spec.rb +2 -4
- data/spec/private/generate_spec.rb +86 -32
- data/spec/private/grapher_spec.rb +5 -6
- data/spec/private/parser_spec.rb +75 -0
- data/spec/private/path_spec.rb +35 -4
- data/spec/private/rack/dispatch_spec.rb +100 -15
- data/spec/private/recognize_spec.rb +88 -50
- data/spec/spec_helper.rb +22 -0
- metadata +13 -7
- data/lib/usher/generate.rb +0 -131
- data/spec/private/split_spec.rb +0 -76
@@ -12,8 +12,7 @@ class Usher
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def reset!
|
15
|
-
@usher ||= Usher.new
|
16
|
-
@url_generator ||= Usher::Generators::URL.new(@usher)
|
15
|
+
@usher ||= Usher.new(:generator => Usher::Util::Generators::URL.new)
|
17
16
|
@module ||= Module.new
|
18
17
|
@module.instance_methods.each do |selector|
|
19
18
|
@module.class_eval { remove_method selector }
|
@@ -38,7 +37,7 @@ class Usher
|
|
38
37
|
|
39
38
|
path[0, 0] = '/' unless path[0] == ?/
|
40
39
|
route = @usher.add_route(path, options)
|
41
|
-
raise "your route must include a controller" unless route.paths.first.dynamic_keys.include?(:controller) || route.destination.include?(:controller)
|
40
|
+
raise "your route must include a controller" unless (route.paths.first.dynamic_keys && route.paths.first.dynamic_keys.include?(:controller)) || route.destination.include?(:controller)
|
42
41
|
route
|
43
42
|
end
|
44
43
|
|
@@ -86,7 +85,7 @@ class Usher
|
|
86
85
|
end
|
87
86
|
|
88
87
|
def generate_url(route, params)
|
89
|
-
@
|
88
|
+
@usher.generator.generate(route, params)
|
90
89
|
end
|
91
90
|
|
92
91
|
def path_for_options(options)
|
@@ -28,7 +28,7 @@ class Usher
|
|
28
28
|
path[0, 0] = '/' unless path[0] == ?/
|
29
29
|
route = @router.add_route(path, options).to(options)
|
30
30
|
|
31
|
-
raise "your route must include a controller" unless (route.paths.first.dynamic_keys.include?(:controller) || route.destination.include?(:controller)
|
31
|
+
raise "your route must include a controller" unless (route.paths.first.dynamic_keys && route.paths.first.dynamic_keys.include?(:controller)) || route.destination.include?(:controller)
|
32
32
|
route
|
33
33
|
end
|
34
34
|
|
@@ -72,8 +72,7 @@ class Usher
|
|
72
72
|
end
|
73
73
|
|
74
74
|
def reset!
|
75
|
-
@router = Usher.new
|
76
|
-
@url_generator = Usher::Generators::URL.new(@router)
|
75
|
+
@router = Usher.new(:generator => Usher::Util::Generators::URL.new)
|
77
76
|
@configuration_files = []
|
78
77
|
@module ||= Module.new
|
79
78
|
@controller_route_added = false
|
@@ -123,7 +122,7 @@ class Usher
|
|
123
122
|
end
|
124
123
|
|
125
124
|
def generate_url(route, params)
|
126
|
-
@
|
125
|
+
@router.generator.generate(route, params)
|
127
126
|
end
|
128
127
|
|
129
128
|
def path_for_options(options)
|
@@ -0,0 +1,57 @@
|
|
1
|
+
class Usher
|
2
|
+
module Interface
|
3
|
+
class Rails3Interface
|
4
|
+
|
5
|
+
@@instance = nil
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@usher = Usher.new
|
9
|
+
@controller_paths = []
|
10
|
+
@configurations_files = []
|
11
|
+
|
12
|
+
@@instance = self
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.instance
|
16
|
+
@@instance
|
17
|
+
end
|
18
|
+
|
19
|
+
def draw(&blk)
|
20
|
+
@usher.instance_eval(&blk)
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_accessor :controller_paths
|
24
|
+
|
25
|
+
def add_configuration_file(file)
|
26
|
+
@configurations_files << file
|
27
|
+
end
|
28
|
+
|
29
|
+
def reload!
|
30
|
+
@usher.reset!
|
31
|
+
@configurations_files.each do |c|
|
32
|
+
Kernel.load(c)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def call(env)
|
37
|
+
request = ActionDispatch::Request.new(env)
|
38
|
+
response = @usher.recognize(request, request.path_info)
|
39
|
+
request.parameters.merge!(response.path.route.default_values) if response.path.route.default_values
|
40
|
+
response.params.each{ |hk| request.parameters[hk.first] = hk.last}
|
41
|
+
controller = "#{request.parameters[:controller].to_s.camelize}Controller".constantize
|
42
|
+
controller.action(request.parameters[:action] || 'index').call(env)
|
43
|
+
end
|
44
|
+
|
45
|
+
def recognize(request)
|
46
|
+
params = recognize_path(request.path, extract_request_environment(request))
|
47
|
+
request.path_parameters = params.with_indifferent_access
|
48
|
+
"#{params[:controller].to_s.camelize}Controller".constantize
|
49
|
+
end
|
50
|
+
|
51
|
+
def load(app)
|
52
|
+
@app = app
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/usher/node.rb
CHANGED
@@ -4,15 +4,16 @@ class Usher
|
|
4
4
|
|
5
5
|
class Node
|
6
6
|
|
7
|
-
Response = Struct.new(:path, :params)
|
7
|
+
Response = Struct.new(:path, :params, :remaining_path, :matched_path)
|
8
8
|
|
9
|
-
attr_reader :lookup
|
10
|
-
attr_accessor :terminates, :exclusive_type, :parent, :value, :request_methods
|
9
|
+
attr_reader :lookup, :greedy_lookup
|
10
|
+
attr_accessor :terminates, :exclusive_type, :parent, :value, :request_methods
|
11
11
|
|
12
12
|
def initialize(parent, value)
|
13
13
|
@parent = parent
|
14
14
|
@value = value
|
15
15
|
@lookup = Hash.new
|
16
|
+
@greedy_lookup = Hash.new
|
16
17
|
@exclusive_type = nil
|
17
18
|
end
|
18
19
|
|
@@ -20,14 +21,21 @@ class Usher
|
|
20
21
|
@lookup = FuzzyHash.new(@lookup)
|
21
22
|
end
|
22
23
|
|
24
|
+
def upgrade_greedy_lookup
|
25
|
+
@greedy_lookup = FuzzyHash.new(@greedy_lookup)
|
26
|
+
end
|
27
|
+
|
23
28
|
def depth
|
24
|
-
@depth ||= @parent
|
29
|
+
@depth ||= @parent.is_a?(Node) ? @parent.depth + 1 : 0
|
25
30
|
end
|
26
31
|
|
27
|
-
def
|
32
|
+
def greedy?
|
33
|
+
!@greedy_lookup.empty?
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.root(route_set, request_methods)
|
28
37
|
root = self.new(route_set, nil)
|
29
38
|
root.request_methods = request_methods
|
30
|
-
root.globs_capture_separators = globs_capture_separators
|
31
39
|
root
|
32
40
|
end
|
33
41
|
|
@@ -47,92 +55,132 @@ class Usher
|
|
47
55
|
|
48
56
|
def add(route)
|
49
57
|
route.paths.each do |path|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
current_node.exclusive_type = key.type
|
65
|
-
current_node.lookup[key.value] ||= Node.new(current_node, key)
|
66
|
-
else
|
67
|
-
parts.unshift(key)
|
68
|
-
current_node.lookup[nil] ||= Node.new(current_node, Route::RequestMethod.new(current_node.exclusive_type, nil))
|
69
|
-
end
|
70
|
-
else
|
71
|
-
key.globs_capture_separators = globs_capture_separators if key.is_a?(Route::Variable)
|
72
|
-
|
73
|
-
if !key.is_a?(Route::Variable)
|
74
|
-
current_node.upgrade_lookup if key.is_a?(Regexp)
|
75
|
-
current_node.lookup[key] ||= Node.new(current_node, key)
|
76
|
-
elsif key.regex_matcher
|
77
|
-
current_node.upgrade_lookup
|
78
|
-
current_node.lookup[key.regex_matcher] ||= Node.new(current_node, key)
|
79
|
-
else
|
80
|
-
current_node.lookup[nil] ||= Node.new(current_node, key)
|
81
|
-
end
|
82
|
-
end
|
83
|
-
current_node = target_node
|
84
|
-
end
|
85
|
-
current_node.terminates = path
|
58
|
+
set_path_with_destination(path)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def delete(route)
|
63
|
+
route.paths.each do |path|
|
64
|
+
set_path_with_destination(path, nil)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def unique_routes(node = self, routes = [])
|
69
|
+
routes << node.terminates.route if node.terminates
|
70
|
+
node.lookup.values.each do |v|
|
71
|
+
unique_routes(v, routes)
|
86
72
|
end
|
87
|
-
|
73
|
+
node.greedy_lookup.values.each do |v|
|
74
|
+
unique_routes(v, routes)
|
75
|
+
end
|
76
|
+
routes.uniq!
|
77
|
+
routes
|
88
78
|
end
|
89
79
|
|
90
|
-
def find(usher, request, path, params = [])
|
80
|
+
def find(usher, request, original_path, path, params = [], position = 0)
|
91
81
|
if exclusive_type
|
92
82
|
[lookup[request.send(exclusive_type)], lookup[nil]].each do |n|
|
93
|
-
if n && (ret = n.find(usher, request, path.dup, params.dup))
|
83
|
+
if n && (ret = n.find(usher, request, original_path, path.dup, params.dup, position))
|
94
84
|
return ret
|
95
85
|
end
|
96
86
|
end
|
97
|
-
elsif path.size.zero?
|
98
|
-
|
87
|
+
elsif terminates? && (path.size.zero? || terminates.route.partial_match?)
|
88
|
+
if terminates.route.partial_match?
|
89
|
+
Response.new(terminates, params, original_path[position, original_path.size], original_path[0, position])
|
90
|
+
else
|
91
|
+
Response.new(terminates, params, nil, original_path)
|
92
|
+
end
|
93
|
+
|
94
|
+
elsif !path.size.zero? && (greedy? && (match_with_result_output = greedy_lookup.match_with_result(whole_path = original_path[position, original_path.size])))
|
95
|
+
next_path, matched_part = match_with_result_output
|
96
|
+
position += matched_part.size
|
97
|
+
params << [next_path.value.name, whole_path.slice!(0, matched_part.size)]
|
98
|
+
next_path.find(usher, request, original_path, whole_path.size.zero? ? whole_path : usher.splitter.url_split(whole_path), params, position)
|
99
99
|
elsif !path.size.zero? && (next_part = lookup[part = path.shift] || lookup[nil])
|
100
|
+
position += part.size
|
100
101
|
case next_part.value
|
101
|
-
when Route::Variable
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
path.unshift(next_part.parent.value)
|
109
|
-
|
110
|
-
elsif next_part.value.globs_capture_separators
|
111
|
-
params.last.last << part
|
112
|
-
elsif !usher.splitter.delimiter_chars.include?(part[0])
|
113
|
-
next_part.value.valid!(part)
|
114
|
-
params.last.last << part
|
115
|
-
end
|
116
|
-
if path.size.zero?
|
117
|
-
break
|
118
|
-
else
|
119
|
-
part = path.shift
|
102
|
+
when Route::Variable::Glob
|
103
|
+
params << [next_part.value.name, []] unless params.last && params.last.first == next_part.value.name
|
104
|
+
loop do
|
105
|
+
if (next_part.value.look_ahead === part || (!usher.delimiter_chars.include?(part[0]) && next_part.value.regex_matcher && !next_part.value.regex_matcher.match(part)))
|
106
|
+
path.unshift(part)
|
107
|
+
position -= part.size
|
108
|
+
if usher.delimiter_chars.include?(next_part.parent.value[0])
|
109
|
+
path.unshift(next_part.parent.value)
|
110
|
+
position -= next_part.parent.value.size
|
120
111
|
end
|
112
|
+
break
|
113
|
+
elsif !usher.delimiter_chars.include?(part[0])
|
114
|
+
next_part.value.valid!(part)
|
115
|
+
params.last.last << part
|
121
116
|
end
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
until (var.look_ahead === path.first) || path.empty?
|
127
|
-
params.last.last << path.shift
|
117
|
+
if path.size.zero?
|
118
|
+
break
|
119
|
+
else
|
120
|
+
part = path.shift
|
128
121
|
end
|
129
122
|
end
|
123
|
+
when Route::Variable::Single
|
124
|
+
var = next_part.value
|
125
|
+
var.valid!(part)
|
126
|
+
params << [var.name, part]
|
127
|
+
until (var.look_ahead === path.first) || path.empty?
|
128
|
+
next_path_part = path.shift
|
129
|
+
position += next_path_part.size
|
130
|
+
params.last.last << next_path_part
|
131
|
+
end
|
130
132
|
end
|
131
|
-
next_part.find(usher, request, path, params)
|
133
|
+
next_part.find(usher, request, original_path, path, params, position)
|
132
134
|
else
|
133
135
|
nil
|
134
136
|
end
|
135
137
|
end
|
136
138
|
|
139
|
+
private
|
140
|
+
def set_path_with_destination(path, destination = path)
|
141
|
+
parts = path.parts.dup
|
142
|
+
request_methods.each do |type|
|
143
|
+
parts.push(Route::RequestMethod.new(type, path.route.conditions[type])) if path.route.conditions && path.route.conditions.key?(type)
|
144
|
+
end
|
145
|
+
|
146
|
+
current_node = self
|
147
|
+
until parts.size.zero?
|
148
|
+
key = parts.shift
|
149
|
+
target_node = case key
|
150
|
+
when Route::RequestMethod
|
151
|
+
current_node.upgrade_lookup if key.value.is_a?(Regexp)
|
152
|
+
if current_node.exclusive_type == key.type
|
153
|
+
current_node.lookup[key.value] ||= Node.new(current_node, key)
|
154
|
+
elsif current_node.lookup.empty?
|
155
|
+
current_node.exclusive_type = key.type
|
156
|
+
current_node.lookup[key.value] ||= Node.new(current_node, key)
|
157
|
+
else
|
158
|
+
parts.unshift(key)
|
159
|
+
current_node.lookup[nil] ||= Node.new(current_node, Route::RequestMethod.new(current_node.exclusive_type, nil))
|
160
|
+
end
|
161
|
+
when Route::Variable
|
162
|
+
upgrade_method, lookup_method = case key
|
163
|
+
when Route::Variable::Greedy
|
164
|
+
[:upgrade_greedy_lookup, :greedy_lookup]
|
165
|
+
else
|
166
|
+
[:upgrade_lookup, :lookup]
|
167
|
+
end
|
168
|
+
|
169
|
+
if key.regex_matcher
|
170
|
+
current_node.send(upgrade_method)
|
171
|
+
current_node.send(lookup_method)[key.regex_matcher] ||= Node.new(current_node, key)
|
172
|
+
else
|
173
|
+
current_node.send(lookup_method)[nil] ||= Node.new(current_node, key)
|
174
|
+
end
|
175
|
+
else
|
176
|
+
current_node.upgrade_lookup if key.is_a?(Regexp)
|
177
|
+
current_node.lookup[key] ||= Node.new(current_node, key)
|
178
|
+
end
|
179
|
+
current_node = target_node
|
180
|
+
end
|
181
|
+
current_node.terminates = destination
|
182
|
+
end
|
183
|
+
|
184
|
+
|
137
185
|
end
|
138
186
|
end
|
data/lib/usher/route.rb
CHANGED
@@ -1,20 +1,20 @@
|
|
1
1
|
require File.join(File.dirname(__FILE__), 'route', 'path')
|
2
|
+
require File.join(File.dirname(__FILE__), 'route', 'util')
|
2
3
|
require File.join(File.dirname(__FILE__), 'route', 'variable')
|
3
4
|
require File.join(File.dirname(__FILE__), 'route', 'request_method')
|
4
5
|
|
5
6
|
class Usher
|
6
7
|
class Route
|
7
|
-
attr_reader
|
8
|
+
attr_reader :paths, :requirements, :conditions,
|
9
|
+
:destination, :named, :generate_with,
|
10
|
+
:default_values, :match_partially
|
11
|
+
attr_accessor :parent_route
|
8
12
|
|
9
13
|
GenerateWith = Struct.new(:scheme, :port, :host)
|
10
14
|
|
11
|
-
def initialize(
|
12
|
-
@
|
13
|
-
@router = router
|
14
|
-
@requirements = requirements
|
15
|
-
@conditions = conditions
|
16
|
-
@default_values = default_values
|
17
|
-
@paths = @router.splitter.split(@original_path, @requirements, @default_values).collect {|path| Path.new(self, path)}
|
15
|
+
def initialize(parsed_paths, router, conditions, requirements, default_values, generate_with, match_partially)
|
16
|
+
@paths = parsed_paths.collect {|path| Path.new(self, path)}
|
17
|
+
@router, @requirements, @conditions, @default_values, @match_partially = router, requirements, conditions, default_values, match_partially
|
18
18
|
@generate_with = GenerateWith.new(generate_with[:scheme], generate_with[:port], generate_with[:host]) if generate_with
|
19
19
|
end
|
20
20
|
|
@@ -26,11 +26,27 @@ class Usher
|
|
26
26
|
@grapher
|
27
27
|
end
|
28
28
|
|
29
|
+
def dup
|
30
|
+
result = super
|
31
|
+
result.grapher = nil
|
32
|
+
result
|
33
|
+
end
|
34
|
+
|
29
35
|
def find_matching_path(params)
|
30
|
-
|
36
|
+
if params.nil? || params.empty?
|
37
|
+
matching_path = @paths.first
|
38
|
+
else
|
39
|
+
matching_path = @paths.size == 1 ? @paths.first : grapher.find_matching_path(params)
|
40
|
+
end
|
41
|
+
|
42
|
+
if parent_route
|
43
|
+
matching_path = parent_route.find_matching_path(params).merge(matching_path)
|
44
|
+
matching_path.route = self
|
45
|
+
end
|
46
|
+
|
47
|
+
matching_path
|
31
48
|
end
|
32
49
|
|
33
|
-
|
34
50
|
# Sets +options+ on a route. Returns +self+.
|
35
51
|
#
|
36
52
|
# Request = Struct.new(:path)
|
@@ -39,7 +55,13 @@ class Usher
|
|
39
55
|
# route.to(:controller => 'testing', :action => 'index')
|
40
56
|
# set.recognize(Request.new('/test')).first.params => {:controller => 'testing', :action => 'index'}
|
41
57
|
def to(options = nil, &block)
|
42
|
-
|
58
|
+
raise "cannot set destintaion as block and argument" if block_given? && options
|
59
|
+
@destination = if block_given?
|
60
|
+
block
|
61
|
+
else
|
62
|
+
options.parent_route = self if options.respond_to?(:parent_route=)
|
63
|
+
options
|
64
|
+
end
|
43
65
|
self
|
44
66
|
end
|
45
67
|
|
@@ -53,6 +75,18 @@ class Usher
|
|
53
75
|
@router.name(name, self)
|
54
76
|
self
|
55
77
|
end
|
78
|
+
|
79
|
+
def match_partially!
|
80
|
+
@match_partially = true
|
81
|
+
self
|
82
|
+
end
|
83
|
+
|
84
|
+
def partial_match?
|
85
|
+
@match_partially
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
attr_writer :grapher
|
56
90
|
|
57
91
|
end
|
58
92
|
end
|