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,20 @@
1
+ class Usher
2
+ class Route
3
+ class Path
4
+
5
+ attr_reader :dynamic_parts, :dynamic_map, :dynamic_indicies, :route, :dynamic_set, :parts
6
+
7
+ def initialize(route, parts)
8
+ @route = route
9
+ @parts = parts
10
+ @dynamic_indicies = []
11
+ @parts.each_index{|i| @dynamic_indicies << i if @parts[i].is_a?(Variable)}
12
+ @dynamic_parts = @parts.values_at(*@dynamic_indicies)
13
+ @dynamic_map = {}
14
+ @dynamic_parts.each{|p| @dynamic_map[p.name] = p }
15
+ @dynamic_set = Set.new(@dynamic_map.keys)
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,21 @@
1
+ class Usher
2
+ class Route
3
+
4
+ class Separator
5
+ private
6
+ def initialize(sep)
7
+ @sep = sep
8
+ @sep_to_s = "#{sep}"
9
+ end
10
+
11
+ public
12
+ def to_s
13
+ @sep_to_s
14
+ end
15
+
16
+ Dot = Separator.new(:'.')
17
+ Slash = Separator.new(:/)
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,93 @@
1
+ class Usher
2
+ class Route
3
+ class Splitter
4
+
5
+ ScanRegex = /([:\*]?[0-9a-z_]+|\/|\.|\(|\))/
6
+ UrlScanRegex = /\/|\.|\w+/
7
+
8
+ attr_reader :paths
9
+
10
+ def initialize(path, requirements = {}, transformers = {})
11
+ @parts = Splitter.split(path, requirements, transformers)
12
+ @paths = calc_paths(@parts)
13
+ end
14
+
15
+ def self.url_split(path)
16
+ parts = path[0] == ?/ ? [] : [Separator::Slash]
17
+ ss = StringScanner.new(path)
18
+ while !ss.eos?
19
+ part = ss.scan(UrlScanRegex)
20
+ case part[0]
21
+ when ?.
22
+ parts << Separator::Dot
23
+ when ?/
24
+ parts << Separator::Slash
25
+ else
26
+ parts << part
27
+ end
28
+ end if path && !path.empty?
29
+ parts
30
+ end
31
+
32
+ def self.split(path, requirements = {}, transformers = {})
33
+ parts = path[0] == ?/ ? [] : [Separator::Slash]
34
+ ss = StringScanner.new(path)
35
+ groups = [parts]
36
+ current_group = parts
37
+ while !ss.eos?
38
+ part = ss.scan(ScanRegex)
39
+ case part[0]
40
+ when ?*, ?:
41
+ type = part.slice!(0).chr.to_sym
42
+ current_group << Variable.new(type, part, :validator => requirements[part.to_sym], :transformer => transformers[part.to_sym])
43
+ when ?.
44
+ current_group << Separator::Dot
45
+ when ?/
46
+ current_group << Separator::Slash
47
+ when ?(
48
+ new_group = []
49
+ groups << new_group
50
+ current_group << new_group
51
+ current_group = new_group
52
+ when ?)
53
+ groups.pop
54
+ current_group = groups.last
55
+ else
56
+ current_group << part
57
+ end
58
+ end unless !path || path.empty?
59
+ parts
60
+ end
61
+
62
+ private
63
+ def calc_paths(parts)
64
+ paths = []
65
+ optional_parts = []
66
+ parts.each_index {|i| optional_parts << i if parts[i].is_a?(Array)}
67
+ if optional_parts.size.zero?
68
+ [parts]
69
+ else
70
+ (0...(2 << (optional_parts.size - 1))).each do |i|
71
+ current_paths = [[]]
72
+ parts.each_index do |part_index|
73
+ part = parts[part_index]
74
+ if optional_parts.include?(part_index) && (2 << (optional_parts.index(part_index)-1) & i != 0)
75
+ new_sub_parts = calc_paths(part)
76
+ current_paths_size = current_paths.size
77
+ (new_sub_parts.size - 1).times {|i| current_paths << current_paths[i % current_paths_size].dup }
78
+ current_paths.each_index do |current_path_idx|
79
+ current_paths[current_path_idx].push(*new_sub_parts[current_path_idx % new_sub_parts.size])
80
+ end
81
+ elsif !optional_parts.include?(part_index)
82
+ current_paths.each { |current_path| current_path << part }
83
+ end
84
+ end
85
+ paths.push(*current_paths)
86
+ end
87
+ paths
88
+ end
89
+ end
90
+
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,22 @@
1
+ class Usher
2
+ class Route
3
+ class Variable
4
+ attr_reader :type, :name, :validator, :transformer
5
+
6
+ def initialize(type, name, opts = {})
7
+ @type = type
8
+ @name = :"#{name}"
9
+ @validator = opts[:validator]
10
+ @transformer = opts[:transformer]
11
+ end
12
+
13
+ def to_s
14
+ "#{type}#{name}"
15
+ end
16
+
17
+ def ==(o)
18
+ o && (o.type == @type && o.name == @name && o.validator == @validator)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,4 @@
1
+ class Usher::Interface::Rails2Interface::Mapper
2
+ include ActionController::Resources
3
+ end
4
+ ActionController::Routing.module_eval "remove_const(:Routes); Routes = Usher::Interface.for(:rails2);"
@@ -0,0 +1,61 @@
1
+ require 'lib/usher'
2
+
3
+ route_set = Usher.new
4
+
5
+ describe "Usher URL generation" do
6
+
7
+ before(:each) do
8
+ route_set.reset!
9
+ end
10
+
11
+ it "should generate a simple URL" do
12
+ route_set.add_named_route(:sample, '/sample', :controller => 'sample', :action => 'action')
13
+ route_set.generate_url(:sample, {}).should == '/sample'
14
+ end
15
+
16
+ it "should generate a simple URL with a single variable" do
17
+ route_set.add_named_route(:sample, '/sample/:action', :controller => 'sample')
18
+ route_set.generate_url(:sample, {:action => 'action'}).should == '/sample/action'
19
+ end
20
+
21
+ it "should generate a simple URL with a single variable (thats not a string)" do
22
+ route_set.add_named_route(:sample, '/sample/:action/:id', :controller => 'sample')
23
+ route_set.generate_url(:sample, {:action => 'action', :id => 123}).should == '/sample/action/123'
24
+ end
25
+
26
+ it "should generate a simple URL with a glob variable" do
27
+ route_set.add_named_route(:sample, '/sample/*action', :controller => 'sample')
28
+ route_set.generate_url(:sample, {:action => ['foo', 'baz']}).should == '/sample/foo/baz'
29
+ end
30
+
31
+ it "should generate a mutliple vairable URL from a hash" do
32
+ route_set.add_named_route(:sample, '/sample/:first/:second', :controller => 'sample')
33
+ route_set.generate_url(:sample, {:first => 'zoo', :second => 'maz'}).should == '/sample/zoo/maz'
34
+ end
35
+
36
+ it "should generate a mutliple vairable URL from an array" do
37
+ route_set.add_named_route(:sample, '/sample/:first/:second', :controller => 'sample')
38
+ route_set.generate_url(:sample, ['maz', 'zoo']).should == '/sample/maz/zoo'
39
+ end
40
+
41
+ it "should generate append extra hash variables to the end" do
42
+ route_set.add_named_route(:sample, '/sample/:first/:second', :controller => 'sample')
43
+ route_set.generate_url(:sample, {:first => 'maz', :second => 'zoo', :third => 'zanz'}).should == '/sample/maz/zoo?third=zanz'
44
+ end
45
+
46
+ it "should generate append extra hash variables to the end using [] syntax if its an array" do
47
+ route_set.add_named_route(:sample, '/sample/:first/:second', :controller => 'sample')
48
+ route_set.generate_url(:sample, {:first => 'maz', :second => 'zoo', :third => ['zanz', 'susie']}).should == '/sample/maz/zoo?third%5B%5D=zanz&third%5B%5D=susie'
49
+ end
50
+
51
+ it "should generate a mutliple vairable URL from an array" do
52
+ route_set.add_named_route(:sample, '/sample/:first/:second', :controller => 'sample')
53
+ route_set.generate_url(:sample, ['maz', 'zoo']).should == '/sample/maz/zoo'
54
+ end
55
+
56
+ it "should generate a simple URL with a format" do
57
+ route_set.add_named_route(:sample, '/sample/:action.:format', :controller => 'sample')
58
+ route_set.generate_url(:sample, {:action => 'action', :format => 'html'}).should == '/sample/action.html'
59
+ end
60
+
61
+ end
@@ -0,0 +1,53 @@
1
+ require 'lib/usher'
2
+
3
+
4
+ describe "String/regexp lookup table" do
5
+
6
+ it "should accept strings and retrieve based on them" do
7
+ l = Usher::Node::Lookup.new
8
+ l['asd'] = 'qwe'
9
+ l['asd'].should == 'qwe'
10
+ end
11
+
12
+ it "should accept regexs too" do
13
+ l = Usher::Node::Lookup.new
14
+ l[/asd.*/] = 'qwe'
15
+ l['asdqweasd'].should == 'qwe'
16
+ end
17
+
18
+ it "should prefer string to regex matches" do
19
+ l = Usher::Node::Lookup.new
20
+ l['asd'] = 'qwe2'
21
+ l[/asd.*/] = 'qwe'
22
+ l['asd'].should == 'qwe2'
23
+ end
24
+
25
+ it "should allow nil keys" do
26
+ l = Usher::Node::Lookup.new
27
+ l[nil] = 'qwe2'
28
+ l['asd'] = 'qwe'
29
+ l['asd'].should == 'qwe'
30
+ l[nil].should == 'qwe2'
31
+ end
32
+
33
+ it "should be able to delete by value for hash" do
34
+ l = Usher::Node::Lookup.new
35
+ l[nil] = 'qwe2'
36
+ l['asd'] = 'qwe'
37
+ l['asd'].should == 'qwe'
38
+ l[nil].should == 'qwe2'
39
+ l.delete_value('qwe2')
40
+ l[nil].should == nil
41
+ end
42
+
43
+ it "should be able to delete by value for hash" do
44
+ l = Usher::Node::Lookup.new
45
+ l[/qwe.*/] = 'qwe2'
46
+ l['asd'] = 'qwe'
47
+ l['asd'].should == 'qwe'
48
+ l['qweasd'].should == 'qwe2'
49
+ l.delete_value('qwe2')
50
+ l['qweasd'].should == nil
51
+ end
52
+
53
+ end
@@ -0,0 +1,42 @@
1
+ require 'lib/usher'
2
+
3
+ route_set = Usher.new
4
+
5
+ S = Usher::Route::Separator::Slash
6
+ D = Usher::Route::Separator::Dot
7
+
8
+ describe "Usher route adding" do
9
+
10
+ before(:each) do
11
+ route_set.reset!
12
+ end
13
+
14
+ it "should be empty after a reset" do
15
+ route_set.add_route('/sample', :controller => 'sample')
16
+ route_set.empty?.should == false
17
+ route_set.reset!
18
+ route_set.empty?.should == true
19
+ end
20
+
21
+ it "shouldn't care about routes without a controller" do
22
+ proc { route_set.add_route('/bad/route') }.should_not raise_error
23
+ end
24
+
25
+ it "should add every kind of optional route possible" do
26
+ route_set.add_route('/a/b(/c)(/d(/e))')
27
+ route_set.routes.first.paths.collect{|a| a.parts }.should == [
28
+ [S, "a", S, "b"],
29
+ [S, "a", S, "b", S, "c"],
30
+ [S, "a", S, "b", S, "d"],
31
+ [S, "a", S, "b", S, "d", S, "e"],
32
+ [S, "a", S, "b", S, "c", S, "d"],
33
+ [S, "a", S, "b", S, "c", S, "d", S, "e"]
34
+ ]
35
+
36
+ end
37
+
38
+ it "should allow named routes to be added" do
39
+ route_set.add_named_route(:route, '/bad/route', :controller => 'sample').should == route_set.named_routes[:route]
40
+ end
41
+
42
+ end
@@ -0,0 +1,36 @@
1
+ require 'lib/compat'
2
+ require 'lib/usher'
3
+
4
+ route_set = Usher::Interface.for(:rack)
5
+
6
+ def build_request_mock(path, method, params)
7
+ request = mock "Request"
8
+ request.should_receive(:path).any_number_of_times.and_return(path)
9
+ request.should_receive(:method).any_number_of_times.and_return(method)
10
+ params = params.with_indifferent_access
11
+ request.should_receive(:path_parameters=).any_number_of_times.with(params)
12
+ request.should_receive(:path_parameters).any_number_of_times.and_return(params)
13
+ request
14
+ end
15
+
16
+ def build_app_mock(params)
17
+ request = mock "App"
18
+ request.should_receive(:call).any_number_of_times.with(params)
19
+ request
20
+ end
21
+
22
+ SampleController = Object.new
23
+
24
+ describe "Usher (for rack) route dispatching" do
25
+
26
+ before(:each) do
27
+ route_set.reset!
28
+ end
29
+
30
+ it "should dispatch a simple request" do
31
+ env = {'REQUEST_URI' => '/sample', 'REQUEST_METHOD' => 'get', 'usher.params' => {}}
32
+ route_set.add('/sample', :controller => 'sample', :action => 'action').to(build_app_mock(env.dup))
33
+ route_set.call(env)
34
+ end
35
+
36
+ end
@@ -0,0 +1,28 @@
1
+ require 'lib/compat'
2
+ require 'lib/usher'
3
+
4
+ route_set = Usher::Interface.for(:rails2)
5
+
6
+ describe "Usher (for rails) URL generation" do
7
+
8
+ before(:each) do
9
+ route_set.reset!
10
+ end
11
+
12
+ it "should fill in the controller from recall" do
13
+ route_set.add_route(':controller/:action/:id')
14
+ route_set.generate({:action => 'thingy'}, {:controller => 'sample', :action => 'index', :id => 123}, :generate).should == '/sample/thingy'
15
+ end
16
+
17
+ it "should skip the action if not provided" do
18
+ route_set.add_route(':controller/:action/:id')
19
+ route_set.generate({:controller => 'thingy'}, {:controller => 'sample', :action => 'index', :id => 123}, :generate).should == '/thingy'
20
+ end
21
+
22
+ it "should pick the correct param from optional parts" do
23
+ route_set.add_route(':controller/:action(.:format)')
24
+ route_set.generate({:action => 'thingy', :format => 'html'}, {:controller => 'sample', :action => 'index', :id => 123}, :generate).should == '/sample/thingy.html'
25
+ route_set.generate({:action => 'thingy'}, {:controller => 'sample', :action => 'index', :id => 123}, :generate).should == '/sample/thingy'
26
+ end
27
+
28
+ end
@@ -0,0 +1,16 @@
1
+ require 'lib/compat'
2
+ require 'lib/usher'
3
+
4
+ route_set = Usher::Interface.for(:rails2)
5
+
6
+ describe "Usher (for rails) route adding" do
7
+
8
+ before(:each) do
9
+ route_set.reset!
10
+ end
11
+
12
+ it "shouldn't allow routes without a controller to be added" do
13
+ proc { route_set.add_route('/bad/route') }.should raise_error
14
+ end
15
+
16
+ end
@@ -0,0 +1,79 @@
1
+ require 'lib/compat'
2
+ require 'lib/usher'
3
+ require 'action_controller'
4
+
5
+ route_set = Usher::Interface.for(:rails2)
6
+
7
+ def build_request_mock(path, method, params)
8
+ request = mock "Request"
9
+ request.should_receive(:path).any_number_of_times.and_return(path)
10
+ request.should_receive(:method).any_number_of_times.and_return(method)
11
+ params = params.with_indifferent_access
12
+ request.should_receive(:path_parameters=).any_number_of_times.with(params)
13
+ request.should_receive(:path_parameters).any_number_of_times.and_return(params)
14
+ request
15
+ end
16
+
17
+ SampleController = Object.new
18
+
19
+ describe "Usher (for rails) route recognition" do
20
+
21
+ before(:each) do
22
+ route_set.reset!
23
+ end
24
+
25
+ it "should recognize a simple request" do
26
+ route_set.add_route('/sample', :controller => 'sample', :action => 'action')
27
+ route_set.recognize(build_request_mock('/sample', 'get', {:controller => 'sample', :action => 'action'})).should == SampleController
28
+ end
29
+
30
+ it "should interpolate action :index" do
31
+ route_set.add_route('/sample', :controller => 'sample')
32
+ route_set.recognize(build_request_mock('/sample', 'get', {:controller => 'sample', :action => 'index'})).should == SampleController
33
+ end
34
+
35
+ it "should correctly distinguish between multiple request methods" do
36
+ route_set.add_route('/sample', :controller => 'not_sample', :conditions => {:method => :get})
37
+ correct_route = route_set.add_route('/sample', :controller => 'sample', :conditions => {:method => :post})
38
+ route_set.add_route('/sample', :controller => 'not_sample', :conditions => {:method => :put})
39
+ route_set.recognize(build_request_mock('/sample', :post, {:controller => 'sample', :action => 'index'})).should == SampleController
40
+ end
41
+
42
+ it "should prefer the static route to the dynamic route" do
43
+ route_set.add_route('/sample/:action', :controller => 'not_sample')
44
+ route_set.add_route('/sample/test', :controller => 'sample', :action => 'action')
45
+ route_set.recognize(build_request_mock('/sample/test', 'get', {:controller => 'sample', :action => 'action'})).should == SampleController
46
+ end
47
+
48
+ it "should raise based upon an invalid param" do
49
+ route_set.add_named_route(:sample, '/sample/:action', :controller => 'sample', :requirements => {:action => /\d+/})
50
+ proc { route_set.recognize(build_request_mock('/sample/asdqwe', :post, {})) }.should raise_error
51
+ end
52
+
53
+ it "should raise based upon an invalid route" do
54
+ route_set.add_named_route(:sample, '/sample', :controller => 'sample', :action => 'test')
55
+ proc { route_set.recognize(build_request_mock('/test/asdqwe', :post, {})) }.should raise_error(ActionController::RoutingError)
56
+ end
57
+
58
+ it "should add /:controller and /:controller/:action if /:controller/:action/:id is added" do
59
+ route_set.add_route('/:controller/:action/:id')
60
+ route_set.route_count.should == 3
61
+ end
62
+
63
+ it "should correctly recognize a format (dynamic path path with . delimiter)" do
64
+ route_set.add_route('/:controller/:action/:id.:format')
65
+ route_set.recognize(build_request_mock('/sample/test/123.html', 'get', {:controller => 'sample', :action => 'test', :id => '123', :format => 'html'})).should == SampleController
66
+ end
67
+
68
+ it "should support root nodes" do
69
+ route_set.add_route('/', :controller => 'sample')
70
+ route_set.recognize(build_request_mock('/', :get, {:controller => 'sample', :action => 'index'})).should == SampleController
71
+ end
72
+
73
+ it "should default action to 'index' when controller (and not index) is specified" do
74
+ route_set.add_route('/:controller/:action')
75
+ route_set.recognize(build_request_mock('/sample', :get, {:controller => 'sample', :action => 'index'})).should == SampleController
76
+ end
77
+
78
+
79
+ end