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