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