joshbuddy-usher 0.1.2 → 0.2.0

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