joshbuddy-usher 0.5.4 → 0.5.6
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 +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
|