joshbuddy-usher 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -6,14 +6,52 @@ This is a tree-based router (based on Ilya Grigorik suggestion). Turns out looki
6
6
 
7
7
  == Route format
8
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 ...
9
+ From the rdoc:
10
+
11
+ <tt>+path+</tt>::
12
+ A path consists a mix of dynamic and static parts delimited by <tt>/</tt>
13
+
14
+ *Dynamic*
15
+
16
+ Dynamic parts are prefixed with either :, *. :variable matches only one part of the path, whereas *variable can match one or
17
+ more parts.
18
+
19
+ Example:
20
+ <tt>/path/:variable/path</tt> would match
21
+
22
+ * <tt>/path/test/path</tt>
23
+ * <tt>/path/something_else/path</tt>
24
+ * <tt>/path/one_more/path</tt>
25
+
26
+ In the above examples, 'test', 'something_else' and 'one_more' respectively would be bound to the key <tt>:variable</tt>.
27
+ However, <tt>/path/test/one_more/path</tt> would not be matched.
28
+
29
+ Example:
30
+ <tt>/path/*variable/path</tt> would match
31
+
32
+ * <tt>/path/one/two/three/path</tt>
33
+ * <tt>/path/four/five/path</tt>
34
+
35
+ In the above examples, ['one', 'two', 'three'] and ['four', 'five'] respectively would be bound to the key :variable.
36
+
37
+ *Static*
38
+
39
+ Static parts of literal character sequences. For instance, <tt>/path/something.html</tt> would match only the same path.
40
+
41
+ <b>Optional sections</b>
42
+
43
+ Sections of a route can be marked as optional by surrounding it with brackets. For instance, in the above static example, <tt>/path/something(.html)</tt> would match both <tt>/path/something</tt> and <tt>/path/something.html</tt>.
44
+
45
+ <b>One and only one sections</b>
46
+
47
+ Sections of a route can be marked as "one and only one" by surrounding it with brackets and separating parts of the route with pipes. For instance, the path, <tt>/path/something(.xml|.html)</tt> would only match <tt>/path/something.xml</tt> and <tt>/path/something.html</tt>.
48
+
49
+ <tt>+options+</tt>::
50
+ --
51
+ * :transformers - Transforms a variable before it gets to the conditions and requirements. Takes either a +proc+ or a +symbol+. If its a +symbol+, calls the method on the incoming parameter. If its a +proc+, its called with the variable.
52
+ * :requirements - After transformation, tests the condition using ===. If it returns false, it raises an <tt>Usher::ValidationException</tt>
53
+ * :conditions - Accepts any of the following <tt>:protocol</tt>, <tt>:domain</tt>, <tt>:port</tt>, <tt>:query_string</tt>, <tt>:remote_ip</tt>, <tt>:user_agent</tt>, <tt>:referer</tt> and <tt>:method</tt>. This can be either a <tt>string</tt> or a <tt>regexp</tt>.
54
+ * any other key is interpreted as a requirement for the variable of its name.
17
55
 
18
56
  == Rails
19
57
 
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
- :patch: 2
3
2
  :major: 0
4
- :minor: 1
3
+ :minor: 2
4
+ :patch: 0
@@ -2,7 +2,7 @@ class Usher
2
2
  class Route
3
3
  class Splitter
4
4
 
5
- ScanRegex = /((:|\*||\.:|\.)[0-9a-z_]+|\/|\(|\))/
5
+ ScanRegex = /((:|\*||\.:|\.)[0-9a-z_]+|\/|\(|\)|\|)/
6
6
  UrlScanRegex = /\/|\.?\w+/
7
7
 
8
8
  attr_reader :paths
@@ -10,6 +10,7 @@ class Usher
10
10
  def initialize(path, requirements = {}, transformers = {})
11
11
  @parts = Splitter.split(path, requirements, transformers)
12
12
  @paths = calc_paths(@parts)
13
+ @paths
13
14
  end
14
15
 
15
16
  def self.url_split(path)
@@ -24,9 +25,8 @@ class Usher
24
25
  end
25
26
 
26
27
  def self.split(path, requirements = {}, transformers = {})
27
- parts = []
28
+ parts = Group.new(:all, nil)
28
29
  ss = StringScanner.new(path)
29
- groups = [parts]
30
30
  current_group = parts
31
31
  while !ss.eos?
32
32
  part = ss.scan(ScanRegex)
@@ -35,13 +35,22 @@ class Usher
35
35
  type = (part[1] == ?: ? part.slice!(0,2) : part.slice!(0).chr).to_sym
36
36
  current_group << Variable.new(type, part, :validator => requirements[part.to_sym], :transformer => transformers[part.to_sym])
37
37
  when ?(
38
- new_group = []
39
- groups << new_group
38
+ new_group = Group.new(:any, current_group)
40
39
  current_group << new_group
41
40
  current_group = new_group
42
- when ?)
43
- groups.pop
44
- current_group = groups.last
41
+ when ?)
42
+ current_group = current_group.parent
43
+ when ?|
44
+ unless current_group.parent.type == :one
45
+ detached_group = current_group.parent.pop
46
+ new_group = Group.new(:one, detached_group.parent)
47
+ detached_group.parent = new_group
48
+ detached_group.type = :all
49
+ new_group << detached_group
50
+ new_group.parent << new_group
51
+ end
52
+ current_group.parent << Group.new(:all, current_group.parent)
53
+ current_group = current_group.parent.last
45
54
  when ?/
46
55
  else
47
56
  current_group << part
@@ -49,33 +58,57 @@ class Usher
49
58
  end unless !path || path.empty?
50
59
  parts
51
60
  end
52
-
61
+
53
62
  private
63
+
64
+ def cartesian_product!(lval, rval)
65
+ product = []
66
+ (lval.size * rval.size).times do |index|
67
+ val = []
68
+ val.push(*lval[index % lval.size])
69
+ val.push(*rval[index % rval.size])
70
+ product << val
71
+ end
72
+ lval.replace(product)
73
+ end
74
+
54
75
  def calc_paths(parts)
55
- paths = []
56
- optional_parts = []
57
- parts.each_index {|i| optional_parts << i if parts[i].is_a?(Array)}
58
- if optional_parts.size.zero?
59
- [parts]
60
- else
61
- (0...(2 << (optional_parts.size - 1))).each do |i|
62
- current_paths = [[]]
63
- parts.each_index do |part_index|
64
- part = parts[part_index]
65
- if optional_parts.include?(part_index) && (2 << (optional_parts.index(part_index)-1) & i != 0)
66
- new_sub_parts = calc_paths(part)
67
- current_paths_size = current_paths.size
68
- (new_sub_parts.size - 1).times {|i| current_paths << current_paths[i % current_paths_size].dup }
69
- current_paths.each_index do |current_path_idx|
70
- current_paths[current_path_idx].push(*new_sub_parts[current_path_idx % new_sub_parts.size])
71
- end
72
- elsif !optional_parts.include?(part_index)
73
- current_paths.each { |current_path| current_path << part }
74
- end
76
+ if parts.is_a?(Group)
77
+ paths = [[]]
78
+ case parts.type
79
+ when :all
80
+ parts.each do |p|
81
+ cartesian_product!(paths, calc_paths(p))
82
+ end
83
+ when :any
84
+ parts.each do |p|
85
+ cartesian_product!(paths, calc_paths(p))
75
86
  end
76
- paths.push(*current_paths)
87
+ paths.unshift([])
88
+ when :one
89
+ cartesian_product!(paths, parts.collect do |p|
90
+ calc_paths(p)
91
+ end)
77
92
  end
93
+ paths.each{|p| p.compact!; p.flatten! }
78
94
  paths
95
+ else
96
+ [[parts]]
97
+ end
98
+
99
+ end
100
+
101
+ class Group < Array
102
+ attr_accessor :type
103
+ attr_accessor :parent
104
+
105
+ def inspect
106
+ "#{type}->#{super}"
107
+ end
108
+
109
+ def initialize(type, parent)
110
+ @type = type
111
+ @parent = parent
79
112
  end
80
113
  end
81
114
 
data/lib/usher.rb CHANGED
@@ -9,7 +9,7 @@ require 'usher/exceptions'
9
9
  class Usher
10
10
  attr_reader :tree, :named_routes, :route_count, :routes
11
11
 
12
- SymbolArraySorter = proc {|a,b| a.hash <=> b.hash}
12
+ SymbolArraySorter = proc {|a,b| a.hash <=> b.hash} #:nodoc:
13
13
 
14
14
  # Returns whether the route set is empty
15
15
  #
@@ -63,33 +63,47 @@ class Usher
63
63
  #
64
64
  # <tt>+path+</tt>::
65
65
  # A path consists a mix of dynamic and static parts delimited by <tt>/</tt>
66
- #
66
+ #
67
67
  # *Dynamic*
68
68
  #
69
69
  # Dynamic parts are prefixed with either :, *. :variable matches only one part of the path, whereas *variable can match one or
70
- # more parts. Example:
71
- # <b>/path/:variable/path</b> would match
72
- #
73
- # * /path/test/path
74
- # * /path/something_else/path
75
- # * /path/one_more/path
76
- #
77
- # In the above examples, 'test', 'something_else' and 'one_more' respectively would be bound to the key :variable.
78
- # However, /path/test/one_more/path would not be matched.
79
- #
70
+ # more parts.
71
+ #
72
+ # Example:
73
+ # <tt>/path/:variable/path</tt> would match
74
+ #
75
+ # * <tt>/path/test/path</tt>
76
+ # * <tt>/path/something_else/path</tt>
77
+ # * <tt>/path/one_more/path</tt>
78
+ #
79
+ # In the above examples, 'test', 'something_else' and 'one_more' respectively would be bound to the key <tt>:variable</tt>.
80
+ # However, <tt>/path/test/one_more/path</tt> would not be matched.
81
+ #
80
82
  # Example:
81
- # <b>/path/*variable/path</b> would match
82
- #
83
- # * /path/one/two/three/path
84
- # * /path/four/five/path
85
- #
83
+ # <tt>/path/*variable/path</tt> would match
84
+ #
85
+ # * <tt>/path/one/two/three/path</tt>
86
+ # * <tt>/path/four/five/path</tt>
87
+ #
86
88
  # In the above examples, ['one', 'two', 'three'] and ['four', 'five'] respectively would be bound to the key :variable.
87
- #
89
+ #
90
+ # *Static*
91
+ #
92
+ # Static parts of literal character sequences. For instance, <tt>/path/something.html</tt> would match only the same path.
93
+ #
94
+ # <b>Optional sections</b>
95
+ #
96
+ # Sections of a route can be marked as optional by surrounding it with brackets. For instance, in the above static example, <tt>/path/something(.html)</tt> would match both <tt>/path/something</tt> and <tt>/path/something.html</tt>.
97
+ #
98
+ # <b>One and only one sections</b>
99
+ #
100
+ # Sections of a route can be marked as "one and only one" by surrounding it with brackets and separating parts of the route with pipes. For instance, the path, <tt>/path/something(.xml|.html)</tt> would only match <tt>/path/something.xml</tt> and <tt>/path/something.html</tt>.
101
+ #
88
102
  # <tt>+options+</tt>::
89
103
  # --
90
- # * :transformers - Transforms a variable before it gets to the conditions and requirements. Takes either a +proc+ or a +symbol+. If its a +symbol+, calls the method on the incoming parameter. If its a +proc+, its called with the variable.
91
- # * :requirements - After transformation, tests the condition using ===. If it returns false, it raises an +Usher::ValidationException+
92
- # * :conditions - Accepts any of the following +:protocol+, +:domain+, +:port+, +:query_string+, +:remote_ip+, +:user_agent+, +:referer+ and +:method+. This can be either a +string+ or a +regexp+.
104
+ # * :transformers - Transforms a variable before it gets to the requirements. Takes either a +proc+ or a +symbol+. If its a +symbol+, calls the method on the incoming parameter. If its a +proc+, its called with the variable.
105
+ # * :requirements - After transformation, tests the condition using ===. If it returns false, it raises an <tt>Usher::ValidationException</tt>
106
+ # * :conditions - Accepts any of the following <tt>:protocol</tt>, <tt>:domain</tt>, <tt>:port</tt>, <tt>:query_string</tt>, <tt>:remote_ip</tt>, <tt>:user_agent</tt>, <tt>:referer</tt> and <tt>:method</tt>. This can be either a <tt>string</tt> or a <tt>regexp</tt>.
93
107
  # * any other key is interpreted as a requirement for the variable of its name.
94
108
  def add_route(path, options = {})
95
109
  transformers = options.delete(:transformers) || {}
@@ -180,16 +194,10 @@ class Usher
180
194
  case v
181
195
  when Array
182
196
  v.each do |v_part|
183
- generated_path << (has_query ? '&' : has_query = true && '?')
184
- generated_path << CGI.escape("#{k.to_s}[]")
185
- generated_path << '='
186
- generated_path << CGI.escape(v_part.to_s)
197
+ generated_path << (has_query ? '&' : has_query = true && '?') << CGI.escape("#{k.to_s}[]") << '=' << CGI.escape(v_part.to_s)
187
198
  end
188
199
  else
189
- generated_path << (has_query ? '&' : has_query = true && '?')
190
- generated_path << CGI.escape(k.to_s)
191
- generated_path << '='
192
- generated_path << CGI.escape(v.to_s)
200
+ generated_path << (has_query ? '&' : has_query = true && '?') << CGI.escape(k.to_s) << '=' << CGI.escape(v.to_s)
193
201
  end
194
202
  end
195
203
  end
data/spec/path_spec.rb CHANGED
@@ -23,10 +23,10 @@ describe "Usher route adding" do
23
23
  route_set.add_route('/a/b(/c)(/d(/e))')
24
24
  route_set.routes.first.paths.collect{|a| a.parts }.should == [
25
25
  ["a", "b"],
26
+ ["a", "b", "c", "d"],
27
+ ["a", "b", "d", "e"],
26
28
  ["a", "b", "c"],
27
29
  ["a", "b", "d"],
28
- ["a", "b", "d", "e"],
29
- ["a", "b", "c", "d"],
30
30
  ["a", "b", "c", "d", "e"]
31
31
  ]
32
32
 
@@ -1,4 +1,3 @@
1
- require 'lib/compat'
2
1
  require 'lib/usher'
3
2
 
4
3
  route_set = Usher::Interface.for(:rack)
@@ -1,4 +1,4 @@
1
- require 'lib/compat'
1
+ require File.join(File.dirname(__FILE__), 'compat')
2
2
  require 'lib/usher'
3
3
 
4
4
  route_set = Usher::Interface.for(:rails2)
@@ -1,4 +1,4 @@
1
- require 'lib/compat'
1
+ require File.join(File.dirname(__FILE__), 'compat')
2
2
  require 'lib/usher'
3
3
 
4
4
  route_set = Usher::Interface.for(:rails2)
@@ -1,4 +1,4 @@
1
- require 'lib/compat'
1
+ require File.join(File.dirname(__FILE__), 'compat')
2
2
  require 'lib/usher'
3
3
  require 'action_controller'
4
4
 
data/spec/split_spec.rb CHANGED
@@ -14,6 +14,21 @@ describe "Usher route tokenizing" do
14
14
  ]
15
15
  end
16
16
 
17
+ it "should group exclusive optional parts with brackets and pipes" do
18
+ Usher::Route::Splitter.new('/test/this(/split|/split2)').paths.should == [
19
+ ['test', 'this', 'split'],
20
+ ['test', 'this', 'split2']
21
+ ]
22
+ end
23
+
24
+ it "should group exclusive optional-optional parts with brackets and pipes" do
25
+ Usher::Route::Splitter.new('/test/this((/split|/split2))').paths.should == [
26
+ ['test', 'this'],
27
+ ['test', 'this', 'split'],
28
+ ['test', 'this', 'split2']
29
+ ]
30
+ end
31
+
17
32
  it "should group optional parts with brackets (for non overlapping groups)" do
18
33
  Usher::Route::Splitter.new('/test/this(/split)(/split2)').paths == [
19
34
  ["test", "this"],
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: joshbuddy-usher
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua Hull
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-03-28 00:00:00 -07:00
12
+ date: 2009-03-31 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -36,7 +36,6 @@ files:
36
36
  - Rakefile
37
37
  - README.rdoc
38
38
  - VERSION.yml
39
- - lib/compat.rb
40
39
  - lib/usher
41
40
  - lib/usher/exceptions.rb
42
41
  - lib/usher/grapher.rb
@@ -65,6 +64,7 @@ files:
65
64
  - spec/rack
66
65
  - spec/rack/dispatch_spec.rb
67
66
  - spec/rails
67
+ - spec/rails/compat.rb
68
68
  - spec/rails/generate_spec.rb
69
69
  - spec/rails/path_spec.rb
70
70
  - spec/rails/recognize_spec.rb
File without changes