joshbuddy-usher 0.0.2
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/History.txt +3 -0
- data/Manifest.txt +35 -0
- data/README.rdoc +63 -0
- data/Rakefile +27 -0
- data/lib/compat.rb +1 -0
- data/lib/usher.rb +135 -0
- data/lib/usher/exceptions.rb +4 -0
- data/lib/usher/grapher.rb +44 -0
- data/lib/usher/interface.rb +22 -0
- data/lib/usher/interface/merb_interface.rb +63 -0
- data/lib/usher/interface/rack_interface.rb +31 -0
- data/lib/usher/interface/rack_interface/mapper.rb +0 -0
- data/lib/usher/interface/rack_interface/route.rb +9 -0
- data/lib/usher/interface/rails2_interface.rb +133 -0
- data/lib/usher/interface/rails2_interface/mapper.rb +44 -0
- data/lib/usher/node.rb +149 -0
- data/lib/usher/node/lookup.rb +78 -0
- data/lib/usher/route.rb +33 -0
- data/lib/usher/route/http.rb +21 -0
- data/lib/usher/route/path.rb +20 -0
- data/lib/usher/route/separator.rb +21 -0
- data/lib/usher/route/splitter.rb +93 -0
- data/lib/usher/route/variable.rb +22 -0
- data/rails/init.rb +4 -0
- data/spec/generate_spec.rb +61 -0
- data/spec/node/lookup_spec.rb +53 -0
- data/spec/path_spec.rb +42 -0
- data/spec/rack/dispatch_spec.rb +36 -0
- data/spec/rails/generate_spec.rb +28 -0
- data/spec/rails/path_spec.rb +16 -0
- data/spec/rails/recognize_spec.rb +79 -0
- data/spec/recognize_spec.rb +66 -0
- data/spec/spec.opts +7 -0
- data/spec/split_spec.rb +37 -0
- data/usher.gemspec +32 -0
- metadata +98 -0
@@ -0,0 +1,133 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__)
|
2
|
+
|
3
|
+
require 'rails2_interface/mapper'
|
4
|
+
|
5
|
+
class Usher
|
6
|
+
module Interface
|
7
|
+
class Rails2Interface
|
8
|
+
|
9
|
+
attr_reader :usher
|
10
|
+
attr_accessor :configuration_file
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
reset!
|
14
|
+
end
|
15
|
+
|
16
|
+
def reset!
|
17
|
+
@usher ||= Usher.new
|
18
|
+
@module ||= Module.new
|
19
|
+
@module.instance_methods.each do |selector|
|
20
|
+
@module.class_eval { remove_method selector }
|
21
|
+
end
|
22
|
+
@controller_action_route_added = false
|
23
|
+
@controller_route_added = false
|
24
|
+
@usher.reset!
|
25
|
+
end
|
26
|
+
|
27
|
+
def add_route(path, options = {})
|
28
|
+
if !@controller_action_route_added && path =~ %r{^/?:controller/:action/:id$}
|
29
|
+
add_route('/:controller/:action', options.dup)
|
30
|
+
@controller_action_route_added = true
|
31
|
+
end
|
32
|
+
|
33
|
+
if !@controller_route_added && path =~ %r{^/?:controller/:action$}
|
34
|
+
add_route('/:controller', options.merge({:action => 'index'}))
|
35
|
+
@controller_route_added = true
|
36
|
+
end
|
37
|
+
|
38
|
+
options[:action] = 'index' unless options[:action]
|
39
|
+
|
40
|
+
route = @usher.add_route(path, options)
|
41
|
+
raise "your route must include a controller" unless route.primary_path.dynamic_set.include?(:controller) || route.params.include?(:controller)
|
42
|
+
route
|
43
|
+
end
|
44
|
+
|
45
|
+
def recognize(request)
|
46
|
+
(path, params_list) = @usher.recognize(request)
|
47
|
+
params = params_list.inject({}){|h,(k,v)| h[k]=v; h }
|
48
|
+
request.path_parameters = (params_list.empty? ? path.route.params : path.route.params.merge(params)).with_indifferent_access
|
49
|
+
"#{request.path_parameters[:controller].camelize}Controller".constantize
|
50
|
+
rescue
|
51
|
+
raise ActionController::RoutingError, "No route matches #{request.path.inspect} with #{request.inspect}"
|
52
|
+
end
|
53
|
+
|
54
|
+
def add_named_route(name, route, options = {})
|
55
|
+
@usher.add_route(route, options).name(name)
|
56
|
+
end
|
57
|
+
|
58
|
+
def route_count
|
59
|
+
@usher.route_count
|
60
|
+
end
|
61
|
+
|
62
|
+
def empty?
|
63
|
+
@usher.route_count.zero?
|
64
|
+
end
|
65
|
+
|
66
|
+
def generate(options, recall = {}, method = :generate, route_name = nil)
|
67
|
+
route = if(route_name)
|
68
|
+
@usher.named_routes[route_name]
|
69
|
+
else
|
70
|
+
merged_options = options
|
71
|
+
merged_options[:controller] = recall[:controller] unless options.key?(:controller)
|
72
|
+
unless options.key?(:action)
|
73
|
+
options[:action] = nil
|
74
|
+
end
|
75
|
+
route_for_options(merged_options)
|
76
|
+
end
|
77
|
+
case method
|
78
|
+
when :generate
|
79
|
+
merged_options ||= recall.merge(options)
|
80
|
+
generate_url(route, merged_options)
|
81
|
+
else
|
82
|
+
raise "method #{method} not recognized"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def generate_url(route, params)
|
87
|
+
@usher.generate_url(route, params)
|
88
|
+
end
|
89
|
+
|
90
|
+
def route_for_options(options)
|
91
|
+
@usher.route_for_options(options)
|
92
|
+
end
|
93
|
+
|
94
|
+
def named_routes
|
95
|
+
@usher.named_routes
|
96
|
+
end
|
97
|
+
|
98
|
+
def reload
|
99
|
+
@usher.reset!
|
100
|
+
if @configuration_file
|
101
|
+
Kernel.load(@configuration_file)
|
102
|
+
else
|
103
|
+
@usher.add_route ":controller/:action/:id"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def load_routes!
|
108
|
+
reload
|
109
|
+
end
|
110
|
+
|
111
|
+
def draw
|
112
|
+
reset!
|
113
|
+
yield Mapper.new(self)
|
114
|
+
install_helpers
|
115
|
+
end
|
116
|
+
|
117
|
+
def install_helpers(destinations = [ActionController::Base, ActionView::Base], regenerate_code = false)
|
118
|
+
#*_url and hash_for_*_url
|
119
|
+
Array(destinations).each do |d| d.module_eval { include Helpers }
|
120
|
+
@usher.named_routes.keys.each do |name|
|
121
|
+
@module.module_eval <<-end_eval # We use module_eval to avoid leaks
|
122
|
+
def #{name}_url(options = {})
|
123
|
+
ActionController::Routing::UsherRoutes.generate(options, {}, :generate, :#{name})
|
124
|
+
end
|
125
|
+
end_eval
|
126
|
+
end
|
127
|
+
d.__send__(:include, @module)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
class Usher
|
2
|
+
module Interface
|
3
|
+
class Rails2Interface
|
4
|
+
|
5
|
+
class Mapper #:doc:
|
6
|
+
def initialize(set) #:nodoc:
|
7
|
+
@set = set
|
8
|
+
end
|
9
|
+
|
10
|
+
def connect(path, options = {})
|
11
|
+
@set.add_route(path, options)
|
12
|
+
end
|
13
|
+
|
14
|
+
def root(options = {})
|
15
|
+
if options.is_a?(Symbol)
|
16
|
+
if source_route = @set.named_routes[options]
|
17
|
+
options = source_route.conditions.blank? ?
|
18
|
+
source_route.options.merge({ :conditions => source_route.conditions }) : source_route.options
|
19
|
+
end
|
20
|
+
end
|
21
|
+
named_route(:root, '/', options)
|
22
|
+
end
|
23
|
+
|
24
|
+
def named_route(name, path, options = {})
|
25
|
+
@set.add_named_route(name, path, options)
|
26
|
+
end
|
27
|
+
|
28
|
+
def namespace(name, options = {}, &block)
|
29
|
+
if options[:namespace]
|
30
|
+
with_options({:path_prefix => "#{options.delete(:path_prefix)}/#{name}", :name_prefix => "#{options.delete(:name_prefix)}#{name}_", :namespace => "#{options.delete(:namespace)}#{name}/" }.merge(options), &block)
|
31
|
+
else
|
32
|
+
with_options({:path_prefix => name, :name_prefix => "#{name}_", :namespace => "#{name}/" }.merge(options), &block)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def method_missing(route_name, *args, &proc) #:nodoc:
|
37
|
+
super unless args.length >= 1 && proc.nil?
|
38
|
+
@set.add_named_route(route_name, *args)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/usher/node.rb
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__)
|
2
|
+
|
3
|
+
require 'node/lookup'
|
4
|
+
|
5
|
+
class Usher
|
6
|
+
|
7
|
+
class Node
|
8
|
+
|
9
|
+
ConditionalTypes = [:protocol, :domain, :port, :query_string, :remote_ip, :user_agent, :referer, :method]
|
10
|
+
|
11
|
+
attr_reader :lookup
|
12
|
+
attr_accessor :terminates, :exclusive_type, :parent, :value
|
13
|
+
|
14
|
+
def initialize(parent, value)
|
15
|
+
@parent = parent
|
16
|
+
@value = value
|
17
|
+
@lookup = Lookup.new
|
18
|
+
@exclusive_type = nil
|
19
|
+
@has_globber = find_parent{|p| p.value && p.value.is_a?(Route::Variable)}
|
20
|
+
end
|
21
|
+
|
22
|
+
def depth
|
23
|
+
unless @depth
|
24
|
+
@depth = 0
|
25
|
+
p = self
|
26
|
+
while (p = p.parent) && p.is_a?(Node)
|
27
|
+
@depth += 1
|
28
|
+
end
|
29
|
+
end
|
30
|
+
@depth
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.root(route_set)
|
34
|
+
self.new(route_set, nil)
|
35
|
+
end
|
36
|
+
|
37
|
+
def has_globber?
|
38
|
+
@has_globber
|
39
|
+
end
|
40
|
+
|
41
|
+
def terminates?
|
42
|
+
@terminates
|
43
|
+
end
|
44
|
+
|
45
|
+
def find_parent(&blk)
|
46
|
+
if @parent.nil? || !@parent.is_a?(Node)
|
47
|
+
nil
|
48
|
+
elsif yield @parent
|
49
|
+
@parent
|
50
|
+
else #keep searching
|
51
|
+
@parent.find_parent(&blk)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def replace(src, dest)
|
56
|
+
@lookup.replace(src, dest)
|
57
|
+
end
|
58
|
+
|
59
|
+
def pp
|
60
|
+
$stdout << " " * depth
|
61
|
+
$stdout << "#{depth}: #{value.inspect} #{!!terminates?}\n"
|
62
|
+
@lookup.each do |k,v|
|
63
|
+
$stdout << " " * (depth + 1)
|
64
|
+
$stdout << "#{k} ==> \n"
|
65
|
+
v.pp
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def add(route)
|
70
|
+
route.paths.each do |path|
|
71
|
+
parts = path.parts.dup
|
72
|
+
ConditionalTypes.each do |type|
|
73
|
+
parts.push(Route::Http.new(type, route.conditions[type])) if route.conditions[type]
|
74
|
+
end
|
75
|
+
|
76
|
+
current_node = self
|
77
|
+
until parts.size.zero?
|
78
|
+
key = parts.shift
|
79
|
+
target_node = case key
|
80
|
+
when Route::Http
|
81
|
+
if current_node.exclusive_type == key.type
|
82
|
+
current_node.lookup[key.value] ||= Node.new(current_node, key)
|
83
|
+
elsif current_node.lookup.empty?
|
84
|
+
current_node.exclusive_type = key.type
|
85
|
+
current_node.lookup[key.value] ||= Node.new(current_node, key)
|
86
|
+
else
|
87
|
+
parts.unshift(key)
|
88
|
+
current_node.lookup[nil] ||= Node.new(current_node, Route::Http.new(current_node.exclusive_type, nil))
|
89
|
+
end
|
90
|
+
else
|
91
|
+
if current_node.exclusive_type
|
92
|
+
parts.unshift(key)
|
93
|
+
current_node.lookup[nil] ||= Node.new(current_node, Route::Http.new(current_node.exclusive_type, nil))
|
94
|
+
else
|
95
|
+
current_node.lookup[key.is_a?(Route::Variable) ? nil : key] ||= Node.new(current_node, key)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
current_node = target_node
|
99
|
+
end
|
100
|
+
current_node.terminates = path
|
101
|
+
end
|
102
|
+
route
|
103
|
+
end
|
104
|
+
|
105
|
+
def find(request, path = Route::Splitter.url_split(request.path), params = [])
|
106
|
+
part = path.shift unless path.size.zero?
|
107
|
+
if @exclusive_type
|
108
|
+
path.unshift part
|
109
|
+
[@lookup[request.send(@exclusive_type)], @lookup[nil]].each do |n|
|
110
|
+
ret = n.find(request, path.dup, params.dup) if n
|
111
|
+
ret and return ret
|
112
|
+
end
|
113
|
+
elsif path.size.zero? && !part
|
114
|
+
if terminates?
|
115
|
+
[terminates, params]
|
116
|
+
else
|
117
|
+
nil
|
118
|
+
end
|
119
|
+
elsif next_part = @lookup[part]
|
120
|
+
next_part.find(request, path, params)
|
121
|
+
elsif next_part = @lookup[nil]
|
122
|
+
if next_part.value.is_a?(Route::Variable)
|
123
|
+
case t = next_part.value.transformer
|
124
|
+
when Proc
|
125
|
+
part = t.call(part)
|
126
|
+
when Symbol
|
127
|
+
part = part.send(t)
|
128
|
+
end
|
129
|
+
part =
|
130
|
+
raise "#{part} does not conform to #{next_part.value.validator}" if next_part.value.validator && (not next_part.value.validator === part)
|
131
|
+
case next_part.value.type
|
132
|
+
when :*
|
133
|
+
params << [next_part.value.name, []]
|
134
|
+
params.last.last << part unless next_part.is_a?(Route::Separator)
|
135
|
+
when :':'
|
136
|
+
params << [next_part.value.name, part]
|
137
|
+
end
|
138
|
+
end
|
139
|
+
next_part.find(request, path, params)
|
140
|
+
elsif has_globber? && p = find_parent{|p| !p.is_a?(Route::Separator)} && p.value.is_a?(Route::Variable) && p.value.type == :*
|
141
|
+
params.last.last << part unless part.is_a?(Route::Separator)
|
142
|
+
find(request, path, params)
|
143
|
+
else
|
144
|
+
nil
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
class Usher
|
2
|
+
class Node
|
3
|
+
class Lookup
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@hash = {}
|
7
|
+
@regexes = []
|
8
|
+
@hash_reverse = {}
|
9
|
+
@regexes_reverse = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def empty?
|
13
|
+
@hash.empty? && @regexes.empty?
|
14
|
+
end
|
15
|
+
|
16
|
+
def keys
|
17
|
+
@hash.keys + @regexes.collect{|r| r.first}
|
18
|
+
end
|
19
|
+
|
20
|
+
def values
|
21
|
+
@hash.values + @regexes.collect{|r| r.last}
|
22
|
+
end
|
23
|
+
|
24
|
+
def each
|
25
|
+
@hash.each{|k,v| yield k,v }
|
26
|
+
@regexes.each{|v| yield v.first, v.last }
|
27
|
+
end
|
28
|
+
|
29
|
+
def delete_value(value)
|
30
|
+
@hash.delete(@hash_reverse[value]) || ((rr = @regexes_reverse[value]) && @regexes.delete_at(rr[0]))
|
31
|
+
end
|
32
|
+
|
33
|
+
def []=(key, value)
|
34
|
+
case key
|
35
|
+
when Regexp
|
36
|
+
@regexes << [key, value]
|
37
|
+
@regex_test = nil
|
38
|
+
@regexes_reverse[value] = [@regexes.size - 1, key, value]
|
39
|
+
else
|
40
|
+
@hash[key] = value
|
41
|
+
@hash_reverse[value] = key
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def replace(src, dest)
|
46
|
+
if @hash_reverse.key?(src)
|
47
|
+
key = @hash_reverse[src]
|
48
|
+
@hash[key] = dest
|
49
|
+
@hash_reverse.delete(src)
|
50
|
+
@hash_reverse[dest] = key
|
51
|
+
elsif @regexes_reverse.key?(src)
|
52
|
+
key = @regexes_reverse[src]
|
53
|
+
@regexes[rkey[0]] = [rkey[1], dest]
|
54
|
+
@regexes_reverse.delete(src)
|
55
|
+
@regexes_reverse[dest] = [rkey[0], rkey[1], dest]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def [](key)
|
60
|
+
@hash[key] || regex_lookup(key)
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
def regex_test
|
65
|
+
@regex_test ||= Regexp.union(*@regexes.collect{|r| r[0]})
|
66
|
+
end
|
67
|
+
|
68
|
+
def regex_lookup(key)
|
69
|
+
if !@regexes.empty? && key.is_a?(String) && data = regex_test.match(key)
|
70
|
+
(data_array = data.to_a).each_index do |i|
|
71
|
+
break @regexes[i].last if data_array.at(i)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/lib/usher/route.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__)
|
2
|
+
|
3
|
+
require 'route/path'
|
4
|
+
require 'route/splitter'
|
5
|
+
require 'route/separator'
|
6
|
+
require 'route/variable'
|
7
|
+
require 'route/http'
|
8
|
+
|
9
|
+
class Usher
|
10
|
+
class Route
|
11
|
+
attr_reader :paths, :original_path, :requirements, :conditions, :params, :primary_path
|
12
|
+
|
13
|
+
def initialize(original_path, router, options = {})
|
14
|
+
@original_path = original_path
|
15
|
+
@router = router
|
16
|
+
@requirements = options.delete(:requirements)
|
17
|
+
@conditions = options.delete(:conditions)
|
18
|
+
@transformers = options.delete(:transformers)
|
19
|
+
@paths = Splitter.new(@original_path, @requirements, @transformers).paths.collect {|path| Path.new(self, path)}
|
20
|
+
@primary_path = @paths.first
|
21
|
+
end
|
22
|
+
|
23
|
+
def to(options)
|
24
|
+
@params = options
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def name(name)
|
29
|
+
@router.name(name, self)
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class Usher
|
2
|
+
class Route
|
3
|
+
class Http
|
4
|
+
|
5
|
+
attr_reader :type, :value
|
6
|
+
|
7
|
+
def initialize(type, value)
|
8
|
+
@type = type
|
9
|
+
@value = value
|
10
|
+
end
|
11
|
+
|
12
|
+
def hash
|
13
|
+
type.hash + value.hash
|
14
|
+
end
|
15
|
+
|
16
|
+
def eql?(o)
|
17
|
+
o.is_a?(Http) && o.type == type && o.value == value
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|