joshbuddy-usher 0.5.4 → 0.5.6
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +6 -6
- data/VERSION.yml +2 -2
- data/lib/usher.rb +45 -37
- data/lib/usher/interface.rb +3 -0
- data/lib/usher/interface/rack_interface.rb +61 -30
- data/lib/usher/interface/rails3_interface.rb +2 -1
- data/lib/usher/interface/text_interface.rb +44 -0
- data/lib/usher/node.rb +17 -13
- data/lib/usher/route.rb +16 -14
- data/lib/usher/util/generate.rb +120 -79
- data/lib/usher/util/rack-mixins.rb +24 -0
- data/spec/private/generate_spec.rb +78 -25
- data/spec/private/rack/dispatch_spec.rb +61 -29
- data/spec/private/rack/generate_spec.rb +41 -0
- data/spec/private/recognize_spec.rb +20 -5
- metadata +6 -2
@@ -0,0 +1,44 @@
|
|
1
|
+
class Usher
|
2
|
+
module Interface
|
3
|
+
class TextInterface
|
4
|
+
|
5
|
+
def initialize(&blk)
|
6
|
+
@usher = Usher.new(:delimiters => [' '], :generator => Usher::Util::Generators::Generic.new)
|
7
|
+
instance_eval(&blk) if blk
|
8
|
+
end
|
9
|
+
|
10
|
+
def generate(name, params = nil)
|
11
|
+
@usher.generator.generate(name, params)
|
12
|
+
end
|
13
|
+
|
14
|
+
def on(text, name = nil, &blk)
|
15
|
+
r = @usher.add_route(text).to(:block => blk, :arg_type => :array)
|
16
|
+
r.name(name) if name
|
17
|
+
end
|
18
|
+
|
19
|
+
def unrecognized(&blk)
|
20
|
+
@unrecognize_block = blk
|
21
|
+
end
|
22
|
+
|
23
|
+
def on_with_hash(text, name = nil, &blk)
|
24
|
+
r = @usher.add_route(text).to(:block => blk, :arg_type => :hash)
|
25
|
+
r.name(name) if name
|
26
|
+
end
|
27
|
+
|
28
|
+
def run(text)
|
29
|
+
response = @usher.recognize_path(text.strip)
|
30
|
+
if response
|
31
|
+
case response.path.route.destination[:arg_type]
|
32
|
+
when :hash
|
33
|
+
response.path.route.destination[:block].call(response.params.inject({}){|h,(k,v)| h[k]=v; h })
|
34
|
+
when :array
|
35
|
+
response.path.route.destination[:block].call(*response.params.collect{|p| p.last})
|
36
|
+
end
|
37
|
+
else
|
38
|
+
@unrecognize_block ? @unrecognize_block.call(text) : nil
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/usher/node.rb
CHANGED
@@ -4,7 +4,11 @@ class Usher
|
|
4
4
|
|
5
5
|
class Node
|
6
6
|
|
7
|
-
Response
|
7
|
+
class Response < Struct.new(:path, :params, :remaining_path, :matched_path)
|
8
|
+
def partial_match?
|
9
|
+
!remaining_path.nil?
|
10
|
+
end
|
11
|
+
end
|
8
12
|
|
9
13
|
attr_reader :normal, :greedy, :request
|
10
14
|
attr_accessor :terminates, :request_method_type, :parent, :value, :request_methods
|
@@ -108,15 +112,7 @@ class Usher
|
|
108
112
|
end
|
109
113
|
|
110
114
|
def find(usher, request_object, original_path, path, params = [], position = 0)
|
111
|
-
if
|
112
|
-
if (specific_node = request[request_object.send(request_method_type)]) && (ret = specific_node.find(usher, request_object, original_path, path.dup, params.dup, position))
|
113
|
-
ret
|
114
|
-
elsif (general_node = request[nil]) && (ret = general_node.find(usher, request_object, original_path, path.dup, params.dup, position))
|
115
|
-
ret
|
116
|
-
else
|
117
|
-
nil
|
118
|
-
end
|
119
|
-
elsif terminates? && (path.empty? || terminates.route.partial_match?)
|
115
|
+
if terminates? && (path.empty? || terminates.route.partial_match?)
|
120
116
|
terminates.route.partial_match? ?
|
121
117
|
Response.new(terminates, params, original_path[position, original_path.size], original_path[0, position]) :
|
122
118
|
Response.new(terminates, params, nil, original_path)
|
@@ -130,7 +126,7 @@ class Usher
|
|
130
126
|
case next_part.value
|
131
127
|
when Route::Variable::Glob
|
132
128
|
params << [next_part.value.name, []] unless params.last && params.last.first == next_part.value.name
|
133
|
-
|
129
|
+
while true
|
134
130
|
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)))
|
135
131
|
path.unshift(part)
|
136
132
|
position -= part.size
|
@@ -153,13 +149,21 @@ class Usher
|
|
153
149
|
var = next_part.value
|
154
150
|
var.valid!(part)
|
155
151
|
params << [var.name, part]
|
156
|
-
until (var.look_ahead === path.first)
|
152
|
+
until path.empty? || (var.look_ahead === path.first)
|
157
153
|
next_path_part = path.shift
|
158
154
|
position += next_path_part.size
|
159
155
|
params.last.last << next_path_part
|
160
|
-
end
|
156
|
+
end if var.look_ahead && usher.delimiter_chars.size > 1
|
161
157
|
end
|
162
158
|
next_part.find(usher, request_object, original_path, path, params, position)
|
159
|
+
elsif request_method_type
|
160
|
+
if (specific_node = request[request_object.send(request_method_type)]) && (ret = specific_node.find(usher, request_object, original_path, path.dup, params.dup, position))
|
161
|
+
ret
|
162
|
+
elsif (general_node = request[nil]) && (ret = general_node.find(usher, request_object, original_path, path.dup, params.dup, position))
|
163
|
+
ret
|
164
|
+
else
|
165
|
+
nil
|
166
|
+
end
|
163
167
|
else
|
164
168
|
nil
|
165
169
|
end
|
data/lib/usher/route.rb
CHANGED
@@ -8,16 +8,16 @@ class Usher
|
|
8
8
|
attr_reader :paths, :requirements, :conditions,
|
9
9
|
:destination, :named, :generate_with,
|
10
10
|
:default_values, :match_partially
|
11
|
-
attr_accessor :parent_route
|
12
|
-
|
11
|
+
attr_accessor :parent_route, :router
|
12
|
+
|
13
13
|
GenerateWith = Struct.new(:scheme, :port, :host)
|
14
|
-
|
15
|
-
def initialize(parsed_paths, router, conditions, requirements, default_values, generate_with, match_partially)
|
14
|
+
|
15
|
+
def initialize(parsed_paths, router, conditions, requirements, default_values, generate_with, match_partially)
|
16
16
|
@paths = parsed_paths.collect {|path| Path.new(self, path)}
|
17
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
|
+
|
21
21
|
def grapher
|
22
22
|
unless @grapher
|
23
23
|
@grapher = Grapher.new
|
@@ -28,7 +28,9 @@ class Usher
|
|
28
28
|
|
29
29
|
def dup
|
30
30
|
result = super
|
31
|
-
result.
|
31
|
+
result.instance_eval do
|
32
|
+
@grapher = nil
|
33
|
+
end
|
32
34
|
result
|
33
35
|
end
|
34
36
|
|
@@ -38,17 +40,17 @@ class Usher
|
|
38
40
|
else
|
39
41
|
matching_path = @paths.size == 1 ? @paths.first : grapher.find_matching_path(params)
|
40
42
|
end
|
41
|
-
|
43
|
+
|
42
44
|
if parent_route
|
43
45
|
matching_path = parent_route.find_matching_path(params).merge(matching_path)
|
44
46
|
matching_path.route = self
|
45
47
|
end
|
46
|
-
|
48
|
+
|
47
49
|
matching_path
|
48
50
|
end
|
49
|
-
|
51
|
+
|
50
52
|
# Sets +options+ on a route. Returns +self+.
|
51
|
-
#
|
53
|
+
#
|
52
54
|
# Request = Struct.new(:path)
|
53
55
|
# set = Usher.new
|
54
56
|
# route = set.add_route('/test')
|
@@ -66,7 +68,7 @@ class Usher
|
|
66
68
|
end
|
67
69
|
|
68
70
|
# Sets route as referenceable from +name+. Returns +self+.
|
69
|
-
#
|
71
|
+
#
|
70
72
|
# set = Usher.new
|
71
73
|
# route = set.add_route('/test').name(:route)
|
72
74
|
# set.generate_url(:route) => '/test'
|
@@ -75,16 +77,16 @@ class Usher
|
|
75
77
|
@router.name(name, self)
|
76
78
|
self
|
77
79
|
end
|
78
|
-
|
80
|
+
|
79
81
|
def match_partially!
|
80
82
|
@match_partially = true
|
81
83
|
self
|
82
84
|
end
|
83
|
-
|
85
|
+
|
84
86
|
def partial_match?
|
85
87
|
@match_partially
|
86
88
|
end
|
87
|
-
|
89
|
+
|
88
90
|
private
|
89
91
|
attr_writer :grapher
|
90
92
|
|
data/lib/usher/util/generate.rb
CHANGED
@@ -1,35 +1,46 @@
|
|
1
|
-
|
1
|
+
class Usher
|
2
|
+
module Util
|
3
|
+
class Generators
|
2
4
|
|
3
|
-
|
4
|
-
module Rack
|
5
|
+
class Generic
|
5
6
|
|
6
|
-
|
7
|
+
attr_accessor :usher
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
9
|
+
def generate(name, params)
|
10
|
+
generate_path_for_base_params(@usher.named_routes[name].find_matching_path(params), params)
|
11
|
+
end
|
12
|
+
|
13
|
+
def generate_path_for_base_params(path, params)
|
14
|
+
raise UnrecognizedException.new unless path
|
15
|
+
|
16
|
+
result = ''
|
17
|
+
path.parts.each do |part|
|
18
|
+
case part
|
19
|
+
when Route::Variable::Glob
|
20
|
+
value = (params && params.delete(part.name)) || part.default_value || raise(MissingParameterException.new)
|
21
|
+
value.each_with_index do |current_value, index|
|
22
|
+
part.valid!(current_value)
|
23
|
+
result << current_value.to_s
|
24
|
+
result << usher.delimiters.first if index != value.size - 1
|
25
|
+
end
|
26
|
+
when Route::Variable
|
27
|
+
value = (params && params.delete(part.name)) || part.default_value || raise(MissingParameterException.new)
|
28
|
+
part.valid!(value)
|
29
|
+
result << value.to_s
|
30
|
+
else
|
31
|
+
result << part
|
32
|
+
end
|
33
|
+
end
|
34
|
+
result
|
35
|
+
end
|
14
36
|
|
15
|
-
def uri_unescape(s)
|
16
|
-
gsub(/((?:%[0-9a-fA-F]{2})+)/n){
|
17
|
-
[$1.delete('%')].pack('H*')
|
18
|
-
}
|
19
37
|
end
|
20
|
-
module_function :uri_unescape
|
21
38
|
|
22
|
-
|
23
|
-
end
|
24
|
-
end
|
39
|
+
class URL < Generic
|
25
40
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
class URL
|
31
|
-
|
32
|
-
attr_accessor :usher
|
41
|
+
def initialize
|
42
|
+
require File.join(File.dirname(__FILE__), 'rack-mixins')
|
43
|
+
end
|
33
44
|
|
34
45
|
def generate_full(routing_lookup, request, params = nil)
|
35
46
|
path = path_for_routing_lookup(routing_lookup, params)
|
@@ -37,10 +48,75 @@ class Usher
|
|
37
48
|
result << generate_path(path, params)
|
38
49
|
end
|
39
50
|
|
51
|
+
# Generates a completed URL based on a +route+ or set of optional +params+
|
52
|
+
#
|
53
|
+
# set = Usher.new
|
54
|
+
# route = set.add_named_route(:test_route, '/:controller/:action')
|
55
|
+
# set.generator.generate(nil, {:controller => 'c', :action => 'a'}) == '/c/a' => true
|
56
|
+
# set.generator.generate(:test_route, {:controller => 'c', :action => 'a'}) == '/c/a' => true
|
57
|
+
# set.generator.generate(route.primary_path, {:controller => 'c', :action => 'a'}) == '/c/a' => true
|
40
58
|
def generate(routing_lookup, params = nil)
|
41
59
|
generate_path(path_for_routing_lookup(routing_lookup, params), params)
|
42
60
|
end
|
43
61
|
|
62
|
+
def generate_path(path, params = nil)
|
63
|
+
params = Array(params) if params.is_a?(String)
|
64
|
+
if params.is_a?(Array)
|
65
|
+
given_size = params.size
|
66
|
+
extra_params = params.last.is_a?(Hash) ? params.pop : nil
|
67
|
+
params = Hash[*path.dynamic_parts.inject([]){|a, dynamic_part| a.concat([dynamic_part.name, params.shift || raise(MissingParameterException.new("got #{given_size}, expected #{path.dynamic_parts.size} parameters"))]); a}]
|
68
|
+
params.merge!(extra_params) if extra_params
|
69
|
+
end
|
70
|
+
|
71
|
+
result = Rack::Utils.uri_escape(generate_path_for_base_params(path, params))
|
72
|
+
unless params.nil? || params.empty?
|
73
|
+
extra_params = generate_extra_params(params, result[??])
|
74
|
+
result << extra_params
|
75
|
+
end
|
76
|
+
result
|
77
|
+
end
|
78
|
+
|
79
|
+
def generation_module
|
80
|
+
build_module!
|
81
|
+
@generation_module
|
82
|
+
end
|
83
|
+
|
84
|
+
def build_module!
|
85
|
+
unless @generation_module
|
86
|
+
@generation_module = Module.new
|
87
|
+
@generation_module.module_eval <<-END_EVAL
|
88
|
+
@@generator = nil
|
89
|
+
def self.generator=(generator)
|
90
|
+
@@generator = generator
|
91
|
+
end
|
92
|
+
END_EVAL
|
93
|
+
@generation_module.generator = self
|
94
|
+
|
95
|
+
@generation_module.module_eval <<-END_EVAL
|
96
|
+
def respond_to?(method_name)
|
97
|
+
if match = Regexp.new('^(.*?)_(path|url)$').match(method_name.to_s)
|
98
|
+
@@generator.usher.named_routes.key?(match.group(1))
|
99
|
+
else
|
100
|
+
super
|
101
|
+
end
|
102
|
+
end
|
103
|
+
END_EVAL
|
104
|
+
|
105
|
+
|
106
|
+
usher.named_routes.each do |name, route|
|
107
|
+
@generation_module.module_eval <<-END_EVAL
|
108
|
+
def #{name}_url(name, request, params = nil)
|
109
|
+
@@generator.generate_full(name, request, options)
|
110
|
+
end
|
111
|
+
|
112
|
+
def #{name}_path(name, params = nil)
|
113
|
+
@@generator.generate(name, options)
|
114
|
+
end
|
115
|
+
END_EVAL
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
44
120
|
def generate_start(path, request)
|
45
121
|
result = (path.route.generate_with && path.route.generate_with.scheme || request.scheme).dup
|
46
122
|
result << '://'
|
@@ -53,77 +129,42 @@ class Usher
|
|
53
129
|
end
|
54
130
|
result
|
55
131
|
end
|
56
|
-
|
132
|
+
|
57
133
|
def path_for_routing_lookup(routing_lookup, params = {})
|
58
134
|
path = case routing_lookup
|
59
135
|
when Symbol
|
60
|
-
route = @usher.named_routes[routing_lookup]
|
136
|
+
route = @usher.named_routes[routing_lookup]
|
137
|
+
raise UnrecognizedException unless route
|
61
138
|
route.find_matching_path(params || {})
|
62
139
|
when Route
|
63
|
-
routing_lookup.find_matching_path(params
|
140
|
+
routing_lookup.find_matching_path(params)
|
64
141
|
when nil
|
65
|
-
params.is_a?(Hash) ?
|
142
|
+
params.is_a?(Hash) ? usher.path_for_options(params) : raise
|
66
143
|
when Route::Path
|
67
144
|
routing_lookup
|
68
145
|
end
|
69
146
|
end
|
70
|
-
|
71
|
-
# Generates a completed URL based on a +route+ or set of optional +params+
|
72
|
-
#
|
73
|
-
# set = Usher.new
|
74
|
-
# route = set.add_named_route(:test_route, '/:controller/:action')
|
75
|
-
# set.generate_url(nil, {:controller => 'c', :action => 'a'}) == '/c/a' => true
|
76
|
-
# set.generate_url(:test_route, {:controller => 'c', :action => 'a'}) == '/c/a' => true
|
77
|
-
# set.generate_url(route.primary_path, {:controller => 'c', :action => 'a'}) == '/c/a' => true
|
78
|
-
def generate_path(path, params = nil)
|
79
|
-
raise UnrecognizedException.new unless path
|
80
147
|
|
81
|
-
params = Array(params) if params.is_a?(String)
|
82
|
-
if params.is_a?(Array)
|
83
|
-
given_size = params.size
|
84
|
-
extra_params = params.last.is_a?(Hash) ? params.pop : nil
|
85
|
-
params = Hash[*path.dynamic_parts.inject([]){|a, dynamic_part| a.concat([dynamic_part.name, params.shift || raise(MissingParameterException.new("got #{given_size}, expected #{path.dynamic_parts.size} parameters"))]); a}]
|
86
|
-
params.merge!(extra_params) if extra_params
|
87
|
-
end
|
88
148
|
|
89
|
-
|
90
|
-
|
91
|
-
case part
|
92
|
-
when Route::Variable::Glob
|
93
|
-
value = (params && params.delete(part.name)) || part.default_value || raise(MissingParameterException.new)
|
94
|
-
value.each_with_index do |current_value, index|
|
95
|
-
part.valid!(current_value)
|
96
|
-
result << current_value.to_s
|
97
|
-
result << '/' if index != value.size - 1
|
98
|
-
end
|
99
|
-
when Route::Variable
|
100
|
-
value = (params && params.delete(part.name)) || part.default_value || raise(MissingParameterException.new)
|
101
|
-
part.valid!(value)
|
102
|
-
result << value.to_s
|
103
|
-
else
|
104
|
-
result << part
|
105
|
-
end
|
106
|
-
end
|
107
|
-
result = Rack::Utils.uri_escape(result)
|
149
|
+
def generate_extra_params(params, has_question_mark)
|
150
|
+
extra_params_result = ''
|
108
151
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
v.each do |v_part|
|
115
|
-
result << (has_query ? '&' : has_query = true && '?') << Rack::Utils.escape("#{k.to_s}[]") << '=' << Rack::Utils.escape(v_part.to_s)
|
116
|
-
end
|
117
|
-
else
|
118
|
-
result << (has_query ? '&' : has_query = true && '?') << Rack::Utils.escape(k.to_s) << '=' << Rack::Utils.escape(v.to_s)
|
152
|
+
params.each do |k,v|
|
153
|
+
case v
|
154
|
+
when Array
|
155
|
+
v.each do |v_part|
|
156
|
+
extra_params_result << (has_question_mark ? '&' : has_question_mark = true && '?') << Rack::Utils.escape("#{k.to_s}[]") << '=' << Rack::Utils.escape(v_part.to_s)
|
119
157
|
end
|
158
|
+
else
|
159
|
+
extra_params_result << (has_question_mark ? '&' : has_question_mark = true && '?') << Rack::Utils.escape(k.to_s) << '=' << Rack::Utils.escape(v.to_s)
|
120
160
|
end
|
121
161
|
end
|
122
|
-
|
162
|
+
extra_params_result
|
123
163
|
end
|
124
|
-
|
164
|
+
|
125
165
|
end
|
126
|
-
|
166
|
+
|
127
167
|
end
|
128
168
|
end
|
129
|
-
end
|
169
|
+
end
|
170
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rack'
|
2
|
+
|
3
|
+
unless Rack::Utils.respond_to?(:uri_escape)
|
4
|
+
module Rack
|
5
|
+
|
6
|
+
module Utils
|
7
|
+
|
8
|
+
def uri_escape(s)
|
9
|
+
s.to_s.gsub(/([^:\/?\[\]\-_~\.!\$&'\(\)\*\+,;=@a-zA-Z0-9]+)/n) {
|
10
|
+
'%'<<$1.unpack('H2'*$1.size).join('%').upcase
|
11
|
+
}.tr(' ', '+')
|
12
|
+
end
|
13
|
+
module_function :uri_escape
|
14
|
+
|
15
|
+
def uri_unescape(s)
|
16
|
+
gsub(/((?:%[0-9a-fA-F]{2})+)/n){
|
17
|
+
[$1.delete('%')].pack('H*')
|
18
|
+
}
|
19
|
+
end
|
20
|
+
module_function :uri_unescape
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|