joshbuddy-usher 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|