usher 0.4.8 → 0.5.1
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.
- 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
|