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
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
|