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