joshbuddy-usher 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ == 0.0.1
2
+
3
+ Initial release as gem
@@ -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
@@ -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)
@@ -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
@@ -0,0 +1 @@
1
+ require 'activesupport'
@@ -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,4 @@
1
+ class Usher
2
+ class UnrecognizedException < RuntimeError
3
+ end
4
+ 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
@@ -0,0 +1,9 @@
1
+ class Usher
2
+ module Interface
3
+ class RackInterface
4
+ module Route
5
+
6
+ end
7
+ end
8
+ end
9
+ end