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 +46 -8
- data/VERSION.yml +2 -2
- data/lib/usher/route/splitter.rb +63 -30
- data/lib/usher.rb +37 -29
- data/spec/path_spec.rb +2 -2
- data/spec/rack/dispatch_spec.rb +0 -1
- data/spec/rails/generate_spec.rb +1 -1
- data/spec/rails/path_spec.rb +1 -1
- data/spec/rails/recognize_spec.rb +1 -1
- data/spec/split_spec.rb +15 -0
- metadata +3 -3
- /data/{lib → spec/rails}/compat.rb +0 -0
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
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
data/lib/usher/route/splitter.rb
CHANGED
@@ -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
|
-
|
44
|
-
|
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
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
parts.
|
64
|
-
|
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.
|
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.
|
71
|
-
#
|
72
|
-
#
|
73
|
-
#
|
74
|
-
#
|
75
|
-
# *
|
76
|
-
#
|
77
|
-
#
|
78
|
-
#
|
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
|
-
# <
|
82
|
-
#
|
83
|
-
# *
|
84
|
-
# *
|
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
|
91
|
-
# * :requirements - After transformation, tests the condition using ===. If it returns false, it raises an
|
92
|
-
# * :conditions - Accepts any of the following
|
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
|
|
data/spec/rack/dispatch_spec.rb
CHANGED
data/spec/rails/generate_spec.rb
CHANGED
data/spec/rails/path_spec.rb
CHANGED
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.
|
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-
|
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
|