joshbuddy-usher 0.4.3 → 0.4.5
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/VERSION.yml +1 -1
- data/lib/usher/generate.rb +82 -0
- data/lib/usher/grapher.rb +30 -8
- data/lib/usher/interface/email_interface.rb +1 -1
- data/lib/usher/interface/rack_interface.rb +9 -2
- data/lib/usher/interface/{rails2_interface → rails2_2_interface}/mapper.rb +1 -1
- data/lib/usher/interface/{rails2_interface.rb → rails2_2_interface.rb} +9 -8
- data/lib/usher/interface/rails2_3_interface.rb +137 -0
- data/lib/usher/interface.rb +6 -3
- data/lib/usher/node.rb +15 -15
- data/lib/usher/route/path.rb +7 -5
- data/lib/usher/route/variable.rb +8 -6
- data/lib/usher/route.rb +21 -9
- data/lib/usher/splitter.rb +9 -10
- data/lib/usher.rb +9 -77
- data/rails/init.rb +7 -3
- data/spec/private/generate_spec.rb +70 -41
- data/spec/private/grapher_spec.rb +19 -17
- data/spec/private/path_spec.rb +11 -11
- data/spec/private/{rails → rails2_2}/compat.rb +0 -0
- data/spec/private/{rails → rails2_2}/generate_spec.rb +2 -2
- data/spec/private/{rails → rails2_2}/path_spec.rb +2 -2
- data/spec/private/{rails → rails2_2}/recognize_spec.rb +2 -2
- data/spec/private/rails2_3/compat.rb +1 -0
- data/spec/private/rails2_3/generate_spec.rb +28 -0
- data/spec/private/rails2_3/path_spec.rb +16 -0
- data/spec/private/rails2_3/recognize_spec.rb +79 -0
- data/spec/private/split_spec.rb +19 -19
- metadata +17 -10
data/VERSION.yml
CHANGED
@@ -0,0 +1,82 @@
|
|
1
|
+
class Usher
|
2
|
+
class Generators
|
3
|
+
|
4
|
+
class URL
|
5
|
+
|
6
|
+
def initialize(usher)
|
7
|
+
@usher = usher
|
8
|
+
end
|
9
|
+
|
10
|
+
# Generates a completed URL based on a +route+ or set of optional +params+
|
11
|
+
#
|
12
|
+
# set = Usher.new
|
13
|
+
# route = set.add_named_route(:test_route, '/:controller/:action')
|
14
|
+
# set.generate_url(nil, {:controller => 'c', :action => 'a'}) == '/c/a' => true
|
15
|
+
# set.generate_url(:test_route, {:controller => 'c', :action => 'a'}) == '/c/a' => true
|
16
|
+
# set.generate_url(route.primary_path, {:controller => 'c', :action => 'a'}) == '/c/a' => true
|
17
|
+
def generate(routing_lookup, params = nil, options = nil)
|
18
|
+
delimiter = options && options.key?(:delimiter) ? options.delete(:delimiter) : @usher.delimiters.first
|
19
|
+
|
20
|
+
path = case routing_lookup
|
21
|
+
when Symbol
|
22
|
+
route = @usher.named_routes[routing_lookup]
|
23
|
+
params.is_a?(Hash) ? route.find_matching_path(params) : route.paths.first
|
24
|
+
when Route
|
25
|
+
params.is_a?(Hash) ? routing_lookup.find_matching_path(params) : routing_lookup.paths.first
|
26
|
+
when nil
|
27
|
+
params.is_a?(Hash) ? @usher.path_for_options(params) : raise
|
28
|
+
when Route::Path
|
29
|
+
routing_lookup
|
30
|
+
end
|
31
|
+
|
32
|
+
raise UnrecognizedException.new unless path
|
33
|
+
|
34
|
+
params = Array(params) if params.is_a?(String)
|
35
|
+
if params.is_a?(Array)
|
36
|
+
extra_params = params.last.is_a?(Hash) ? params.pop : nil
|
37
|
+
params = Hash[*path.dynamic_parts.inject([]){|a, dynamic_part| a.concat([dynamic_part.name, params.shift || raise(MissingParameterException.new)]); a}]
|
38
|
+
params.merge!(extra_params) if extra_params
|
39
|
+
end
|
40
|
+
|
41
|
+
result = ''
|
42
|
+
path.parts.each do |part|
|
43
|
+
case part
|
44
|
+
when Route::Variable
|
45
|
+
value = (params && params.delete(part.name)) || part.default_value || raise(MissingParameterException.new)
|
46
|
+
case part.type
|
47
|
+
when :*
|
48
|
+
value.each_with_index do |current_value, index|
|
49
|
+
current_value = current_value.to_s unless current_value.is_a?(String)
|
50
|
+
part.valid!(current_value)
|
51
|
+
result << current_value
|
52
|
+
result << delimiter if index != value.size - 1
|
53
|
+
end
|
54
|
+
when :':'
|
55
|
+
value = value.to_s unless value.is_a?(String)
|
56
|
+
part.valid!(value)
|
57
|
+
result << value
|
58
|
+
end
|
59
|
+
else
|
60
|
+
result << part
|
61
|
+
end
|
62
|
+
end
|
63
|
+
result = URI.escape(result)
|
64
|
+
|
65
|
+
if params && !params.empty?
|
66
|
+
has_query = result[??]
|
67
|
+
params.each do |k,v|
|
68
|
+
case v
|
69
|
+
when Array
|
70
|
+
v.each do |v_part|
|
71
|
+
result << (has_query ? '&' : has_query = true && '?') << CGI.escape("#{k.to_s}[]") << '=' << CGI.escape(v_part.to_s)
|
72
|
+
end
|
73
|
+
else
|
74
|
+
result << (has_query ? '&' : has_query = true && '?') << CGI.escape(k.to_s) << '=' << CGI.escape(v.to_s)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
result
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
data/lib/usher/grapher.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'set'
|
2
|
-
|
3
1
|
class Usher
|
4
2
|
class Grapher
|
5
3
|
|
@@ -11,29 +9,53 @@ class Usher
|
|
11
9
|
@significant_keys = nil
|
12
10
|
@orders = Hash.new{|h,k| h[k] = Hash.new{|h2, k2| h2[k2] = []}}
|
13
11
|
@key_count = Hash.new(0)
|
12
|
+
@cache = {}
|
14
13
|
end
|
15
14
|
|
16
15
|
def add_route(route)
|
17
16
|
route.paths.each do |path|
|
18
|
-
unless path.
|
19
|
-
path.
|
20
|
-
@orders[path.
|
17
|
+
unless path.dynamic_keys.size.zero?
|
18
|
+
path.dynamic_keys.each do |k|
|
19
|
+
@orders[path.dynamic_keys.size][k] << path
|
21
20
|
@key_count[k] += 1
|
22
21
|
end
|
22
|
+
|
23
|
+
dynamic_parts_with_defaults = path.dynamic_parts.select{|part| part.default_value }.map{|dp| dp.name}
|
24
|
+
dynamic_parts_without_defaults = path.dynamic_parts.select{|part| !part.default_value }.map{|dp| dp.name}
|
25
|
+
|
26
|
+
(1...(2 ** (dynamic_parts_with_defaults.size))).each do |i|
|
27
|
+
current_set = dynamic_parts_without_defaults.dup
|
28
|
+
dynamic_parts_with_defaults.each_with_index do |dp, index|
|
29
|
+
current_set << dp unless (index & i) == 0
|
30
|
+
end
|
31
|
+
|
32
|
+
current_set.each do |k|
|
33
|
+
@orders[current_set.size][k] << path
|
34
|
+
@key_count[k] += 1
|
35
|
+
end
|
36
|
+
end
|
23
37
|
end
|
24
38
|
end
|
25
39
|
end
|
26
40
|
|
27
41
|
def significant_keys
|
28
|
-
@significant_keys ||=
|
42
|
+
@significant_keys ||= @key_count.keys.uniq
|
29
43
|
end
|
30
44
|
|
31
45
|
def find_matching_path(params)
|
32
46
|
unless params.empty?
|
33
|
-
set =
|
47
|
+
set = params.keys & significant_keys
|
48
|
+
if cached = @cache[set]
|
49
|
+
return cached
|
50
|
+
end
|
34
51
|
set.size.downto(1) do |o|
|
35
52
|
set.each do |k|
|
36
|
-
@orders[o][k].each { |r|
|
53
|
+
@orders[o][k].each { |r|
|
54
|
+
if r.can_generate_from?(set)
|
55
|
+
@cache[set] = r
|
56
|
+
return r
|
57
|
+
end
|
58
|
+
}
|
37
59
|
end
|
38
60
|
end
|
39
61
|
nil
|
@@ -22,7 +22,7 @@ class Usher
|
|
22
22
|
def act(email)
|
23
23
|
response = @routes.recognize(email, email)
|
24
24
|
if response.path
|
25
|
-
response.path.route.
|
25
|
+
response.path.route.destination.call(response.params.inject({}){|h,(k,v)| h[k]=v.to_s; h })
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
@@ -13,6 +13,7 @@ class Usher
|
|
13
13
|
|
14
14
|
def initialize(&blk)
|
15
15
|
@routes = Usher.new(:request_methods => RequestMethods)
|
16
|
+
@generator = Usher::Generators::URL.new(@routes)
|
16
17
|
instance_eval(&blk) if blk
|
17
18
|
end
|
18
19
|
|
@@ -26,8 +27,14 @@ class Usher
|
|
26
27
|
|
27
28
|
def call(env)
|
28
29
|
response = @routes.recognize(Request.new(env['REQUEST_URI'], env['REQUEST_METHOD'].downcase, env['HTTP_HOST'], env['SERVER_PORT'].to_i, env['rack.url_scheme']))
|
29
|
-
|
30
|
-
response.
|
30
|
+
params = {}
|
31
|
+
response.params.each{ |hk| params[hk.first] = hk.last}
|
32
|
+
env['usher.params'] = params
|
33
|
+
response.path.route.destination.call(env)
|
34
|
+
end
|
35
|
+
|
36
|
+
def generate(route, params = nil, options = nil)
|
37
|
+
@generator.generate(route, params, options)
|
31
38
|
end
|
32
39
|
|
33
40
|
end
|
@@ -1,10 +1,10 @@
|
|
1
1
|
$:.unshift File.dirname(__FILE__)
|
2
2
|
|
3
|
-
require '
|
3
|
+
require 'rails2_2_interface/mapper'
|
4
4
|
|
5
5
|
class Usher
|
6
6
|
module Interface
|
7
|
-
class
|
7
|
+
class Rails2_2Interface
|
8
8
|
|
9
9
|
attr_reader :usher
|
10
10
|
attr_accessor :configuration_file
|
@@ -15,6 +15,7 @@ class Usher
|
|
15
15
|
|
16
16
|
def reset!
|
17
17
|
@usher ||= Usher.new
|
18
|
+
@url_generator ||= Usher::Generators::URL.new(@usher)
|
18
19
|
@module ||= Module.new
|
19
20
|
@module.instance_methods.each do |selector|
|
20
21
|
@module.class_eval { remove_method selector }
|
@@ -39,14 +40,14 @@ class Usher
|
|
39
40
|
|
40
41
|
path[0, 0] = '/' unless path[0] == ?/
|
41
42
|
route = @usher.add_route(path, options)
|
42
|
-
raise "your route must include a controller" unless route.
|
43
|
+
raise "your route must include a controller" unless route.paths.first.dynamic_keys.include?(:controller) || route.destination.include?(:controller)
|
43
44
|
route
|
44
45
|
end
|
45
46
|
|
46
47
|
def recognize(request)
|
47
48
|
node = @usher.recognize(request)
|
48
49
|
params = node.params.inject({}){|h,(k,v)| h[k]=v; h }
|
49
|
-
request.path_parameters = (node.params.empty? ? node.path.route.
|
50
|
+
request.path_parameters = (node.params.empty? ? node.path.route.destination : node.path.route.destination.merge(params)).with_indifferent_access
|
50
51
|
"#{request.path_parameters[:controller].camelize}Controller".constantize
|
51
52
|
rescue
|
52
53
|
raise ActionController::RoutingError, "No route matches #{request.path.inspect} with #{request.inspect}"
|
@@ -73,7 +74,7 @@ class Usher
|
|
73
74
|
unless options.key?(:action)
|
74
75
|
options[:action] = ''
|
75
76
|
end
|
76
|
-
|
77
|
+
path_for_options(merged_options)
|
77
78
|
end
|
78
79
|
case method
|
79
80
|
when :generate
|
@@ -87,11 +88,11 @@ class Usher
|
|
87
88
|
end
|
88
89
|
|
89
90
|
def generate_url(route, params)
|
90
|
-
@
|
91
|
+
@url_generator.generate(route, params)
|
91
92
|
end
|
92
93
|
|
93
|
-
def
|
94
|
-
@usher.
|
94
|
+
def path_for_options(options)
|
95
|
+
@usher.path_for_options(options)
|
95
96
|
end
|
96
97
|
|
97
98
|
def named_routes
|
@@ -0,0 +1,137 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__)
|
2
|
+
|
3
|
+
class Usher
|
4
|
+
module Interface
|
5
|
+
class Rails2_3Interface
|
6
|
+
|
7
|
+
attr_reader :configuration_files
|
8
|
+
|
9
|
+
def named_routes
|
10
|
+
@router.named_routes
|
11
|
+
end
|
12
|
+
|
13
|
+
def add_named_route(name, route, options = {})
|
14
|
+
@router.add_route(route, options).name(name)
|
15
|
+
end
|
16
|
+
|
17
|
+
def add_route(path, options = {})
|
18
|
+
if !@controller_action_route_added && path =~ %r{^/?:controller/:action/:id$}
|
19
|
+
add_route('/:controller/:action', options.dup)
|
20
|
+
@controller_action_route_added = true
|
21
|
+
end
|
22
|
+
|
23
|
+
if !@controller_route_added && path =~ %r{^/?:controller/:action$}
|
24
|
+
add_route('/:controller', options.merge({:action => 'index'}))
|
25
|
+
@controller_route_added = true
|
26
|
+
end
|
27
|
+
|
28
|
+
options[:action] = 'index' unless options[:action]
|
29
|
+
|
30
|
+
path[0, 0] = '/' unless path[0] == ?/
|
31
|
+
route = @router.add_route(path, options).to(options)
|
32
|
+
|
33
|
+
raise "your route must include a controller" unless (route.paths.first.dynamic_keys.include?(:controller) || route.destination.include?(:controller))
|
34
|
+
route
|
35
|
+
end
|
36
|
+
|
37
|
+
def initialize
|
38
|
+
reset!
|
39
|
+
end
|
40
|
+
|
41
|
+
def add_configuration_file(file)
|
42
|
+
@configuration_files << file
|
43
|
+
end
|
44
|
+
|
45
|
+
def reload!
|
46
|
+
if configuration_files.any?
|
47
|
+
configuration_files.each { |config| load(config) }
|
48
|
+
else
|
49
|
+
add_route ":controller/:action/:id"
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
alias_method :reload, :reload!
|
54
|
+
|
55
|
+
def route_count
|
56
|
+
routes.size
|
57
|
+
end
|
58
|
+
|
59
|
+
def routes
|
60
|
+
@router.routes
|
61
|
+
end
|
62
|
+
|
63
|
+
def call(env)
|
64
|
+
request = ActionController::Request.new(env)
|
65
|
+
app = recognize(request)
|
66
|
+
app.call(env).to_a
|
67
|
+
end
|
68
|
+
|
69
|
+
def recognize(request)
|
70
|
+
response = @router.recognize(request)
|
71
|
+
request.path_parameters = (response.params.empty? ? response.path.route.destination : response.path.route.destination.merge(response.params.inject({}){|h,(k,v)| h[k]=v; h })).with_indifferent_access
|
72
|
+
response.params.each { |pair| request.path_parameters[pair.first] = pair.last }
|
73
|
+
"#{request.path_parameters[:controller].camelize}Controller".constantize
|
74
|
+
end
|
75
|
+
|
76
|
+
def reset!
|
77
|
+
@router = Usher.new
|
78
|
+
@url_generator = Usher::Generators::URL.new(@router)
|
79
|
+
@configuration_files = []
|
80
|
+
@module ||= Module.new
|
81
|
+
@controller_route_added = false
|
82
|
+
@controller_action_route_added = false
|
83
|
+
end
|
84
|
+
|
85
|
+
def draw
|
86
|
+
reset!
|
87
|
+
yield ActionController::Routing::RouteSet::Mapper.new(self)
|
88
|
+
install_helpers
|
89
|
+
end
|
90
|
+
|
91
|
+
def install_helpers(destinations = [ActionController::Base, ActionView::Base], regenerate_code = false)
|
92
|
+
#*_url and hash_for_*_url
|
93
|
+
Array(destinations).each do |d| d.module_eval { include Helpers }
|
94
|
+
@router.named_routes.keys.each do |name|
|
95
|
+
@module.module_eval <<-end_eval # We use module_eval to avoid leaks
|
96
|
+
def #{name}_url(options = {})
|
97
|
+
ActionController::Routing::UsherRoutes.generate(options, {}, :generate, :#{name})
|
98
|
+
end
|
99
|
+
end_eval
|
100
|
+
end
|
101
|
+
d.__send__(:include, @module)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def generate(options, recall = {}, method = :generate, route_name = nil)
|
106
|
+
route = if(route_name)
|
107
|
+
@router.named_routes[route_name]
|
108
|
+
else
|
109
|
+
merged_options = options
|
110
|
+
merged_options[:controller] = recall[:controller] unless options.key?(:controller)
|
111
|
+
unless options.key?(:action)
|
112
|
+
options[:action] = ''
|
113
|
+
end
|
114
|
+
path_for_options(merged_options)
|
115
|
+
end
|
116
|
+
case method
|
117
|
+
when :generate
|
118
|
+
merged_options ||= recall.merge(options)
|
119
|
+
url = generate_url(route, merged_options)
|
120
|
+
url.slice!(-1) if url[-1] == ?/
|
121
|
+
url
|
122
|
+
else
|
123
|
+
raise "method #{method} not recognized"
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def generate_url(route, params)
|
128
|
+
@url_generator.generate(route, params)
|
129
|
+
end
|
130
|
+
|
131
|
+
def path_for_options(options)
|
132
|
+
@router.path_for_options(options)
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
data/lib/usher/interface.rb
CHANGED
@@ -2,15 +2,18 @@ $:.unshift File.dirname(__FILE__)
|
|
2
2
|
|
3
3
|
class Usher
|
4
4
|
module Interface
|
5
|
-
autoload :
|
5
|
+
autoload :Rails2_2Interface, 'interface/rails2_2_interface'
|
6
|
+
autoload :Rails2_3Interface, 'interface/rails2_3_interface'
|
6
7
|
autoload :MerbInterface, 'interface/merb_interface'
|
7
8
|
autoload :RackInterface, 'interface/rack_interface'
|
8
9
|
autoload :EmailInterface, 'interface/email_interface'
|
9
10
|
|
10
11
|
def self.for(type, &blk)
|
11
12
|
case type
|
12
|
-
when :
|
13
|
-
|
13
|
+
when :rails2_2
|
14
|
+
Rails2_2Interface.new(&blk)
|
15
|
+
when :rails2_3
|
16
|
+
Rails2_3Interface.new(&blk)
|
14
17
|
when :merb
|
15
18
|
MerbInterface.new(&blk)
|
16
19
|
when :rack
|
data/lib/usher/node.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
$:.unshift File.dirname(__FILE__)
|
2
2
|
|
3
|
-
require 'fuzzy_hash'
|
3
|
+
require '/Users/josh/Development/fuzzy_hash/lib/fuzzy_hash'
|
4
4
|
|
5
5
|
class Usher
|
6
6
|
|
@@ -89,10 +89,10 @@ class Usher
|
|
89
89
|
route
|
90
90
|
end
|
91
91
|
|
92
|
-
def find(request, path, params = [])
|
92
|
+
def find(usher, request, path, params = [])
|
93
93
|
if exclusive_type
|
94
94
|
[lookup[request.send(exclusive_type)], lookup[nil]].each do |n|
|
95
|
-
if n && (ret = n.find(request, path.dup, params.dup))
|
95
|
+
if n && (ret = n.find(usher, request, path.dup, params.dup))
|
96
96
|
return ret
|
97
97
|
end
|
98
98
|
end
|
@@ -105,15 +105,15 @@ class Usher
|
|
105
105
|
when :*
|
106
106
|
params << [next_part.value.name, []] unless params.last && params.last.first == next_part.value.name
|
107
107
|
loop do
|
108
|
-
if (next_part.value.look_ahead === part || (!
|
108
|
+
if (next_part.value.look_ahead === part || (!usher.splitter.delimiter_chars.include?(part[0]) && next_part.value.regex_matcher && !next_part.value.regex_matcher.match(part)))
|
109
109
|
path.unshift(part)
|
110
|
-
path.unshift(next_part.parent.value) if next_part.parent.value
|
110
|
+
path.unshift(next_part.parent.value) if usher.splitter.delimiter_chars.include?(next_part.parent.value[0])
|
111
111
|
break
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
112
|
+
elsif next_part.value.globs_capture_separators
|
113
|
+
params.last.last << part
|
114
|
+
elsif !usher.splitter.delimiter_chars.include?(part[0])
|
115
|
+
next_part.value.valid!(part)
|
116
|
+
params.last.last << part
|
117
117
|
end
|
118
118
|
if path.size.zero?
|
119
119
|
break
|
@@ -122,15 +122,15 @@ class Usher
|
|
122
122
|
end
|
123
123
|
end
|
124
124
|
when :':'
|
125
|
-
next_part.value.valid!(part)
|
126
125
|
var = next_part.value
|
127
|
-
|
128
|
-
|
129
|
-
|
126
|
+
var.valid!(part)
|
127
|
+
params << [var.name, part]
|
128
|
+
until (var.look_ahead === path.first) || path.empty?
|
129
|
+
params.last.last << path.shift
|
130
130
|
end
|
131
131
|
end
|
132
132
|
end
|
133
|
-
next_part.find(request, path, params)
|
133
|
+
next_part.find(usher, request, path, params)
|
134
134
|
else
|
135
135
|
nil
|
136
136
|
end
|