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
data/History.txt
ADDED
data/Manifest.txt
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
History.txt
|
2
|
+
Manifest.txt
|
3
|
+
README.rdoc
|
4
|
+
Rakefile
|
5
|
+
lib/compat.rb
|
6
|
+
lib/usher.rb
|
7
|
+
lib/usher/exceptions.rb
|
8
|
+
lib/usher/grapher.rb
|
9
|
+
lib/usher/interface.rb
|
10
|
+
lib/usher/interface/merb_interface.rb
|
11
|
+
lib/usher/interface/rack_interface.rb
|
12
|
+
lib/usher/interface/rack_interface/mapper.rb
|
13
|
+
lib/usher/interface/rack_interface/route.rb
|
14
|
+
lib/usher/interface/rails2_interface.rb
|
15
|
+
lib/usher/interface/rails2_interface/mapper.rb
|
16
|
+
lib/usher/node.rb
|
17
|
+
lib/usher/node/lookup.rb
|
18
|
+
lib/usher/route.rb
|
19
|
+
lib/usher/route/http.rb
|
20
|
+
lib/usher/route/path.rb
|
21
|
+
lib/usher/route/separator.rb
|
22
|
+
lib/usher/route/splitter.rb
|
23
|
+
lib/usher/route/variable.rb
|
24
|
+
rails/init.rb
|
25
|
+
spec/generate_spec.rb
|
26
|
+
spec/node/lookup_spec.rb
|
27
|
+
spec/path_spec.rb
|
28
|
+
spec/rack/dispatch_spec.rb
|
29
|
+
spec/rails/generate_spec.rb
|
30
|
+
spec/rails/path_spec.rb
|
31
|
+
spec/rails/recognize_spec.rb
|
32
|
+
spec/recognize_spec.rb
|
33
|
+
spec/spec.opts
|
34
|
+
spec/split_spec.rb
|
35
|
+
usher.gemspec
|
data/README.rdoc
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
= Usher
|
2
|
+
|
3
|
+
Tree-based router for Ruby on Rails.
|
4
|
+
|
5
|
+
This is a tree-based router (based on Ilya Grigorik suggestion). Turns out looking up in a hash and following a tree is faster than Krauter's massive regex approach, so why not? I said, Heck Yes, and here we are.
|
6
|
+
|
7
|
+
== Route format
|
8
|
+
|
9
|
+
Here are some examples of routes recognized by usher (so far)
|
10
|
+
|
11
|
+
_Route_ _Matches_
|
12
|
+
/path/to/something /path/to/something
|
13
|
+
/path/:variable/more /path/foo/more, /path/bar/more ...
|
14
|
+
/show/*tags /show/hot /show/hot/coffee /show/some/more/hot/coffee ...
|
15
|
+
/route(/help) /route, /route/help
|
16
|
+
/route(.:format) /route, /route.xml, /route.html ...
|
17
|
+
|
18
|
+
== Rails
|
19
|
+
|
20
|
+
script/plugin install git://github.com/joshbuddy/usher.git
|
21
|
+
|
22
|
+
== Rack
|
23
|
+
|
24
|
+
=== rackup.ru
|
25
|
+
|
26
|
+
require 'usher'
|
27
|
+
app = proc do |env|
|
28
|
+
body = "Hi there #{env['usher.params'][:name]}"
|
29
|
+
[
|
30
|
+
200, # Status code
|
31
|
+
{ # Response headers
|
32
|
+
'Content-Type' => 'text/plain',
|
33
|
+
'Content-Length' => body.size.to_s,
|
34
|
+
},
|
35
|
+
[body] # Response body
|
36
|
+
]
|
37
|
+
end
|
38
|
+
|
39
|
+
routes = Usher::Interface.for(:rack)
|
40
|
+
routes.add('/hello/:name').to(app)
|
41
|
+
run routes
|
42
|
+
|
43
|
+
------------
|
44
|
+
|
45
|
+
>> curl http://127.0.0.1:3000/hello/samueltanders
|
46
|
+
<< Hi there samueltanders
|
47
|
+
|
48
|
+
== DONE
|
49
|
+
|
50
|
+
* add support for () optional parts
|
51
|
+
|
52
|
+
== TODO
|
53
|
+
|
54
|
+
* Add support for arbitrary HTTP header checks
|
55
|
+
* Make it integrate with merb
|
56
|
+
* Make it integrate with rails3
|
57
|
+
* Create decent DSL for use with rack
|
58
|
+
* Emit exceptions inline with relevant interfaces
|
59
|
+
* More RDoc! (optionally cowbell)
|
60
|
+
|
61
|
+
Looks about 20-50% faster than the router Rails ships with for non-trivial cases.
|
62
|
+
|
63
|
+
(Let me show you to your request)
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'hoe'
|
2
|
+
require 'spec'
|
3
|
+
require 'spec/rake/spectask'
|
4
|
+
require 'lib/usher'
|
5
|
+
|
6
|
+
Hoe.new('usher', Usher::Version) do |p|
|
7
|
+
p.author = 'Joshua Hull'
|
8
|
+
p.email = 'joshbuddy@gmail.com'
|
9
|
+
p.summary = 'Tree-based router'
|
10
|
+
p.developer('Joshua Hull', 'joshbuddy@gmail.com')
|
11
|
+
end
|
12
|
+
|
13
|
+
task :spec => 'spec:all'
|
14
|
+
namespace(:spec) do
|
15
|
+
Spec::Rake::SpecTask.new(:all) do |t|
|
16
|
+
t.spec_opts ||= []
|
17
|
+
t.spec_opts << "-rubygems"
|
18
|
+
t.spec_opts << "--options" << "spec/spec.opts"
|
19
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
task :cultivate do
|
25
|
+
system "touch Manifest.txt; rake check_manifest | grep -v \"(in \" | patch"
|
26
|
+
system "rake debug_gem | grep -v \"(in \" > `basename \\`pwd\\``.gemspec"
|
27
|
+
end
|
data/lib/compat.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'activesupport'
|
data/lib/usher.rb
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__)
|
2
|
+
|
3
|
+
require 'strscan'
|
4
|
+
require 'set'
|
5
|
+
require 'usher/node'
|
6
|
+
require 'usher/route'
|
7
|
+
require 'usher/grapher'
|
8
|
+
require 'usher/interface'
|
9
|
+
require 'usher/exceptions'
|
10
|
+
|
11
|
+
class Usher
|
12
|
+
attr_reader :tree, :named_routes, :route_count, :routes
|
13
|
+
|
14
|
+
SymbolArraySorter = proc {|a,b| a.hash <=> b.hash}
|
15
|
+
Version = '0.0.2'
|
16
|
+
|
17
|
+
def empty?
|
18
|
+
@route_count.zero?
|
19
|
+
end
|
20
|
+
|
21
|
+
def lookup
|
22
|
+
@tree.lookup
|
23
|
+
end
|
24
|
+
|
25
|
+
def reset!
|
26
|
+
@tree = Node.root(self)
|
27
|
+
@named_routes = {}
|
28
|
+
@routes = []
|
29
|
+
@route_count = 0
|
30
|
+
Grapher.instance.reset!
|
31
|
+
end
|
32
|
+
alias clear! reset!
|
33
|
+
|
34
|
+
def initialize(mode = :rails)
|
35
|
+
@mode = mode
|
36
|
+
reset!
|
37
|
+
end
|
38
|
+
|
39
|
+
def add_named_route(name, path, options = {})
|
40
|
+
add_route(path, options).name(name)
|
41
|
+
end
|
42
|
+
|
43
|
+
def name(name, route)
|
44
|
+
@named_routes[name] = route.primary_path
|
45
|
+
end
|
46
|
+
|
47
|
+
def add_route(path, options = {})
|
48
|
+
transformers = options.delete(:transformers) || {}
|
49
|
+
conditions = options.delete(:conditions) || {}
|
50
|
+
requirements = options.delete(:requirements) || {}
|
51
|
+
options.delete_if do |k, v|
|
52
|
+
if v.is_a?(Regexp)
|
53
|
+
requirements[k] = v
|
54
|
+
true
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
route = Route.new(path, self, {:transformers => transformers, :conditions => conditions, :requirements => requirements}).to(options)
|
59
|
+
|
60
|
+
@tree.add(route)
|
61
|
+
@routes << route
|
62
|
+
Grapher.instance.add_route(route)
|
63
|
+
@route_count += 1
|
64
|
+
route
|
65
|
+
end
|
66
|
+
|
67
|
+
def recognize(request)
|
68
|
+
@tree.find(request)
|
69
|
+
end
|
70
|
+
|
71
|
+
def route_for_options(options)
|
72
|
+
Grapher.instance.find_matching_path(options)
|
73
|
+
end
|
74
|
+
|
75
|
+
def generate_url(route, params)
|
76
|
+
path = case route
|
77
|
+
when Symbol
|
78
|
+
@named_routes[route]
|
79
|
+
when nil
|
80
|
+
route_for_options(params)
|
81
|
+
else
|
82
|
+
route
|
83
|
+
end
|
84
|
+
|
85
|
+
params_hash = {}
|
86
|
+
param_list = case params
|
87
|
+
when Hash
|
88
|
+
params_hash = params
|
89
|
+
path.dynamic_parts.collect{|k| params_hash.delete(k.name)}
|
90
|
+
when Array
|
91
|
+
params
|
92
|
+
else
|
93
|
+
Array(params)
|
94
|
+
end
|
95
|
+
|
96
|
+
generated_path = ""
|
97
|
+
|
98
|
+
sep_p = nil
|
99
|
+
path.parts.each do |p|
|
100
|
+
case p
|
101
|
+
when Route::Variable
|
102
|
+
case p.type
|
103
|
+
when :*
|
104
|
+
generated_path << sep_p.to_s << param_list.shift * '/'
|
105
|
+
else
|
106
|
+
(dp = param_list.shift) && generated_path << sep_p.to_s << dp.to_s
|
107
|
+
end
|
108
|
+
when Route::Separator
|
109
|
+
sep_p = p
|
110
|
+
else
|
111
|
+
generated_path << sep_p.to_s << p.to_s
|
112
|
+
end
|
113
|
+
end
|
114
|
+
unless params_hash.blank?
|
115
|
+
has_query = generated_path[??]
|
116
|
+
params_hash.each do |k,v|
|
117
|
+
case v
|
118
|
+
when Array
|
119
|
+
v.each do |v_part|
|
120
|
+
generated_path << (has_query ? '&' : has_query = true && '?')
|
121
|
+
generated_path << CGI.escape("#{k.to_s}[]")
|
122
|
+
generated_path << '='
|
123
|
+
generated_path << CGI.escape(v_part.to_s)
|
124
|
+
end
|
125
|
+
else
|
126
|
+
generated_path << (has_query ? '&' : has_query = true && '?')
|
127
|
+
generated_path << CGI.escape(k.to_s)
|
128
|
+
generated_path << '='
|
129
|
+
generated_path << CGI.escape(v.to_s)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
generated_path
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
class Usher
|
4
|
+
class Grapher
|
5
|
+
include Singleton
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
reset!
|
9
|
+
end
|
10
|
+
|
11
|
+
def reset!
|
12
|
+
@significant_keys = nil
|
13
|
+
@orders = Hash.new{|h,k| h[k] = Hash.new{|h2, k2| h2[k2] = []}}
|
14
|
+
@key_count = Hash.new(0)
|
15
|
+
end
|
16
|
+
|
17
|
+
def add_route(route)
|
18
|
+
route.paths.each do |path|
|
19
|
+
unless path.dynamic_set.size.zero?
|
20
|
+
path.dynamic_set.each do |k|
|
21
|
+
@orders[path.dynamic_set.size][k] << path
|
22
|
+
@key_count[k] += 1
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def significant_keys
|
29
|
+
@significant_keys ||= Set.new(@key_count.keys)
|
30
|
+
end
|
31
|
+
|
32
|
+
def find_matching_path(params)
|
33
|
+
unless params.empty?
|
34
|
+
set = Set.new(params.keys) & significant_keys
|
35
|
+
set.size.downto(1) do |o|
|
36
|
+
set.each do |k|
|
37
|
+
@orders[o][k].each { |r| return r if r.dynamic_set.subset?(set) }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
nil
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__)
|
2
|
+
|
3
|
+
class Usher
|
4
|
+
module Interface
|
5
|
+
|
6
|
+
def self.for(type)
|
7
|
+
case type
|
8
|
+
when :rails2
|
9
|
+
require 'interface/rails2_interface'
|
10
|
+
Rails2Interface.new
|
11
|
+
when :merb
|
12
|
+
require 'interface/merb_interface'
|
13
|
+
MerbInterface.new
|
14
|
+
when :rack
|
15
|
+
require 'interface/rack_interface'
|
16
|
+
RackInterface.new
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__)
|
2
|
+
|
3
|
+
require 'merb-core'
|
4
|
+
require 'merb-core/dispatch/router/behavior'
|
5
|
+
|
6
|
+
class Usher
|
7
|
+
module Interface
|
8
|
+
class MerbInterface
|
9
|
+
|
10
|
+
# merb does everything with class methods.
|
11
|
+
|
12
|
+
@root_behavior = ::Merb::Router::Behavior.new.defaults(:action => "index")
|
13
|
+
|
14
|
+
class << self
|
15
|
+
attr_accessor :root_behavior
|
16
|
+
|
17
|
+
UsherRoutes = Usher.new
|
18
|
+
|
19
|
+
def prepare(first = [], last = [], &block)
|
20
|
+
@routes = []
|
21
|
+
root_behavior._with_proxy(&block)
|
22
|
+
@routes = first + @routes + last
|
23
|
+
compile
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def compile
|
28
|
+
routes.each do |r|
|
29
|
+
r.segments
|
30
|
+
end
|
31
|
+
|
32
|
+
#puts r.inspect; UsherRoutes.add_route(r) }
|
33
|
+
#routes.each {|r| }
|
34
|
+
end
|
35
|
+
|
36
|
+
def named_routes
|
37
|
+
UsherRoutes.named_routes
|
38
|
+
end
|
39
|
+
|
40
|
+
def routes
|
41
|
+
UsherRoutes.routes
|
42
|
+
end
|
43
|
+
|
44
|
+
def route_for(request)
|
45
|
+
p request
|
46
|
+
p UsherRoutes.tree
|
47
|
+
UsherRoutes.recognize(request)
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
#class BootLoader < ::Merb::BootLoader
|
53
|
+
#end
|
54
|
+
|
55
|
+
def load_into_merb!
|
56
|
+
::Merb.send(:remove_const, "Router")
|
57
|
+
::Merb.const_set("Router", Usher::Interface::MerbInterface)
|
58
|
+
#::Merb::BootLoader.const_set("Router", Usher::Interface::Merb::BootLoader)
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__)
|
2
|
+
|
3
|
+
require 'rack_interface/route'
|
4
|
+
|
5
|
+
class Usher
|
6
|
+
module Interface
|
7
|
+
class RackInterface
|
8
|
+
|
9
|
+
Request = Struct.new(:path, :method)
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@routes = Usher.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def add(path, options = {})
|
16
|
+
@routes.add_route(path, options)
|
17
|
+
end
|
18
|
+
|
19
|
+
def reset!
|
20
|
+
@routes.reset!
|
21
|
+
end
|
22
|
+
|
23
|
+
def call(env)
|
24
|
+
(path, params) = @routes.recognize(Request.new(env['REQUEST_URI'], env['REQUEST_METHOD']))
|
25
|
+
env['usher.params'] = params.inject({}){|h,(k,v)| h[k]=v; h }
|
26
|
+
path.route.params.call(env)
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
File without changes
|