joshbuddy-usher 0.4.11 → 0.5.1

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
@@ -5,11 +5,12 @@ Tree-based router library. Useful for (specifically) for Rails and Rack, but pro
5
5
  == Features
6
6
 
7
7
  * Understands single and path-globbing variables
8
+ * Understands arbitrary regex variables
8
9
  * Arbitrary HTTP header requirements
9
10
  * No optimization phase, so routes are always alterable after the fact
10
11
  * Understands Proc and Regex transformations, validations
11
12
  * Really, really fast
12
- * Relatively light and happy code-base, should be easy and fun to alter
13
+ * Relatively light and happy code-base, should be easy and fun to alter (it hovers around 1,000 LOC, 800 for the core)
13
14
  * Interface and implementation are separate, encouraging cross-pollination
14
15
 
15
16
  == Route format
data/Rakefile CHANGED
@@ -9,7 +9,7 @@ begin
9
9
  s.homepage = "http://github.com/joshbuddy/usher"
10
10
  s.authors = ["Joshua Hull"]
11
11
  s.files = FileList["[A-Z]*", "{lib,spec,rails}/**/*"]
12
- s.add_dependency 'fuzzyhash', '>=0.0.5'
12
+ s.add_dependency 'fuzzyhash', '>=0.0.6'
13
13
  end
14
14
  rescue LoadError
15
15
  puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
- :patch: 11
3
2
  :major: 0
4
- :minor: 4
3
+ :minor: 5
4
+ :patch: 1
data/lib/usher/grapher.rb CHANGED
@@ -14,7 +14,7 @@ class Usher
14
14
 
15
15
  def add_route(route)
16
16
  route.paths.each do |path|
17
- unless path.dynamic_keys.size.zero?
17
+ if path.dynamic?
18
18
  path.dynamic_keys.each do |k|
19
19
  @orders[path.dynamic_keys.size][k] << path
20
20
  @key_count[k] += 1
@@ -50,12 +50,12 @@ class Usher
50
50
  end
51
51
  set.size.downto(1) do |o|
52
52
  set.each do |k|
53
- @orders[o][k].each { |r|
53
+ @orders[o][k].each do |r|
54
54
  if r.can_generate_from?(set)
55
55
  @cache[set] = r
56
56
  return r
57
57
  end
58
- }
58
+ end
59
59
  end
60
60
  end
61
61
  nil
@@ -3,7 +3,7 @@ class Usher
3
3
  class EmailInterface
4
4
 
5
5
  def initialize(&blk)
6
- @routes = Usher.new(:delimiters => ['@', '-', '.'], :valid_regex => '[\+a-zA-Z0-9]+', :globs_capture_separators => true)
6
+ @routes = Usher.new(:delimiters => ['@', '-', '.'], :valid_regex => '[\+a-zA-Z0-9]+')
7
7
  instance_eval(&blk) if blk
8
8
  end
9
9
 
@@ -38,7 +38,7 @@ class Usher
38
38
 
39
39
  path[0, 0] = '/' unless path[0] == ?/
40
40
  route = @usher.add_route(path, options)
41
- raise "your route must include a controller" unless route.paths.first.dynamic_keys.include?(:controller) || route.destination.include?(:controller)
41
+ raise "your route must include a controller" unless (route.paths.first.dynamic_keys && route.paths.first.dynamic_keys.include?(:controller)) || route.destination.include?(:controller)
42
42
  route
43
43
  end
44
44
 
@@ -28,7 +28,7 @@ class Usher
28
28
  path[0, 0] = '/' unless path[0] == ?/
29
29
  route = @router.add_route(path, options).to(options)
30
30
 
31
- raise "your route must include a controller" unless (route.paths.first.dynamic_keys.include?(:controller) || route.destination.include?(:controller))
31
+ raise "your route must include a controller" unless (route.paths.first.dynamic_keys && route.paths.first.dynamic_keys.include?(:controller)) || route.destination.include?(:controller)
32
32
  route
33
33
  end
34
34
 
data/lib/usher/node.rb CHANGED
@@ -7,7 +7,7 @@ class Usher
7
7
  Response = Struct.new(:path, :params)
8
8
 
9
9
  attr_reader :lookup, :greedy_lookup
10
- attr_accessor :terminates, :exclusive_type, :parent, :value, :request_methods, :globs_capture_separators
10
+ attr_accessor :terminates, :exclusive_type, :parent, :value, :request_methods
11
11
 
12
12
  def initialize(parent, value)
13
13
  @parent = parent
@@ -33,10 +33,9 @@ class Usher
33
33
  !@greedy_lookup.empty?
34
34
  end
35
35
 
36
- def self.root(route_set, request_methods, globs_capture_separators)
36
+ def self.root(route_set, request_methods)
37
37
  root = self.new(route_set, nil)
38
38
  root.request_methods = request_methods
39
- root.globs_capture_separators = globs_capture_separators
40
39
  root
41
40
  end
42
41
 
@@ -77,30 +76,25 @@ class Usher
77
76
  current_node.lookup[nil] ||= Node.new(current_node, Route::RequestMethod.new(current_node.exclusive_type, nil))
78
77
  end
79
78
  else
80
- key.globs_capture_separators = globs_capture_separators if key.is_a?(Route::Variable)
81
-
82
79
  case key
83
80
  when Route::Variable
84
- if key.greedy?
85
- if key.regex_matcher
86
- current_node.upgrade_greedy_lookup
87
- current_node.greedy_lookup[key.regex_matcher] ||= Node.new(current_node, key)
88
- else
89
- current_node.greedy_lookup[nil] ||= Node.new(current_node, key)
90
- end
81
+ (upgrade_method, lookup_method) = case key
82
+ when Route::Variable::Greedy
83
+ [:upgrade_greedy_lookup, :greedy_lookup]
91
84
  else
92
- if key.regex_matcher
93
- current_node.upgrade_lookup
94
- current_node.lookup[key.regex_matcher] ||= Node.new(current_node, key)
95
- else
96
- current_node.lookup[nil] ||= Node.new(current_node, key)
97
- end
85
+ [:upgrade_lookup, :lookup]
98
86
  end
87
+
88
+ if key.regex_matcher
89
+ current_node.send(upgrade_method)
90
+ current_node.send(lookup_method)[key.regex_matcher] ||= Node.new(current_node, key)
91
+ else
92
+ current_node.send(lookup_method)[nil] ||= Node.new(current_node, key)
93
+ end
99
94
  else
100
95
  current_node.upgrade_lookup if key.is_a?(Regexp)
101
96
  current_node.lookup[key] ||= Node.new(current_node, key)
102
97
  end
103
-
104
98
  end
105
99
  current_node = target_node
106
100
  end
@@ -118,48 +112,44 @@ class Usher
118
112
  end
119
113
  elsif path.size.zero? && terminates?
120
114
  Response.new(terminates, params)
121
- elsif !path.size.zero? && (greedy? && ((next_path, matched_part) = greedy_lookup.match_with_result(whole_path = original_path[position, original_path.size])))
115
+ elsif !path.size.zero? && (greedy? && (match_with_result_output = greedy_lookup.match_with_result(whole_path = original_path[position, original_path.size])))
116
+ next_path, matched_part = match_with_result_output
122
117
  position += matched_part.size
123
118
  params << [next_path.value.name, whole_path.slice!(0, matched_part.size)]
124
119
  next_path.find(usher, request, original_path, whole_path.size.zero? ? whole_path : usher.splitter.url_split(whole_path), params, position)
125
120
  elsif !path.size.zero? && (next_part = lookup[part = path.shift] || lookup[nil])
126
121
  position += part.size
127
122
  case next_part.value
128
- when Route::Variable
129
- case next_part.value.type
130
- when :*
131
- params << [next_part.value.name, []] unless params.last && params.last.first == next_part.value.name
132
- loop do
133
- if (next_part.value.look_ahead === part || (!usher.delimiter_chars.include?(part[0]) && next_part.value.regex_matcher && !next_part.value.regex_matcher.match(part)))
134
- path.unshift(part)
135
- position -= part.size
136
- if usher.delimiter_chars.include?(next_part.parent.value[0])
137
- path.unshift(next_part.parent.value)
138
- position -= next_part.parent.value.size
139
- end
140
- break
141
- elsif next_part.value.globs_capture_separators
142
- params.last.last << part
143
- elsif !usher.delimiter_chars.include?(part[0])
144
- next_part.value.valid!(part)
145
- params.last.last << part
146
- end
147
- if path.size.zero?
148
- break
149
- else
150
- part = path.shift
123
+ when Route::Variable::Glob
124
+ params << [next_part.value.name, []] unless params.last && params.last.first == next_part.value.name
125
+ loop do
126
+ if (next_part.value.look_ahead === part || (!usher.delimiter_chars.include?(part[0]) && next_part.value.regex_matcher && !next_part.value.regex_matcher.match(part)))
127
+ path.unshift(part)
128
+ position -= part.size
129
+ if usher.delimiter_chars.include?(next_part.parent.value[0])
130
+ path.unshift(next_part.parent.value)
131
+ position -= next_part.parent.value.size
151
132
  end
133
+ break
134
+ elsif !usher.delimiter_chars.include?(part[0])
135
+ next_part.value.valid!(part)
136
+ params.last.last << part
152
137
  end
153
- when :':'
154
- var = next_part.value
155
- var.valid!(part)
156
- params << [var.name, part]
157
- until (var.look_ahead === path.first) || path.empty?
158
- next_path_part = path.shift
159
- position += next_path_part.size
160
- params.last.last << next_path_part
138
+ if path.size.zero?
139
+ break
140
+ else
141
+ part = path.shift
161
142
  end
162
143
  end
144
+ when Route::Variable::Single
145
+ var = next_part.value
146
+ var.valid!(part)
147
+ params << [var.name, part]
148
+ until (var.look_ahead === path.first) || path.empty?
149
+ next_path_part = path.shift
150
+ position += next_path_part.size
151
+ params.last.last << next_path_part
152
+ end
163
153
  end
164
154
  next_part.find(usher, request, original_path, path, params, position)
165
155
  else
@@ -2,22 +2,48 @@ class Usher
2
2
  class Route
3
3
  class Path
4
4
 
5
- attr_reader :dynamic_parts, :dynamic_map, :dynamic_indicies, :route, :parts, :dynamic_required_keys, :dynamic_keys
5
+ attr_reader :route, :parts
6
6
 
7
7
  def initialize(route, parts)
8
8
  @route = route
9
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_keys = @dynamic_map.keys
16
- @dynamic_required_keys = @dynamic_parts.select{|dp| !dp.default_value}.map{|dp| dp.name}
10
+ @dynamic = @parts.any?{|p| p.is_a?(Variable)}
11
+ end
12
+
13
+ def dynamic_indicies
14
+ unless @dynamic_indicies
15
+ @dynamic_indicies = []
16
+ parts.each_index{|i| @dynamic_indicies << i if parts[i].is_a?(Variable)}
17
+ end
18
+ @dynamic_indicies
19
+ end
20
+
21
+ def dynamic_parts
22
+ @dynamic_parts ||= parts.values_at(*dynamic_indicies)
23
+ end
24
+
25
+ def dynamic_map
26
+ unless @dynamic_map
27
+ @dynamic_map = {}
28
+ dynamic_parts.each{|p| @dynamic_map[p.name] = p }
29
+ end
30
+ @dynamic_map
31
+ end
32
+
33
+ def dynamic_keys
34
+ @dynamic_keys ||= dynamic_map.keys
35
+ end
36
+
37
+ def dynamic_required_keys
38
+ @dynamic_required_keys ||= dynamic_parts.select{|dp| !dp.default_value}.map{|dp| dp.name}
39
+ end
40
+
41
+ def dynamic?
42
+ @dynamic
17
43
  end
18
44
 
19
45
  def can_generate_from?(keys)
20
- (@dynamic_required_keys - keys).size.zero?
46
+ (dynamic_required_keys - keys).size.zero?
21
47
  end
22
48
  end
23
49
  end
@@ -0,0 +1,65 @@
1
+ class Usher
2
+ class Route
3
+
4
+ module Util
5
+
6
+ class Group < Array
7
+ attr_accessor :group_type
8
+ attr_accessor :parent
9
+
10
+ def inspect
11
+ "#{group_type}->#{super}"
12
+ end
13
+
14
+ def initialize(group_type, parent)
15
+ @group_type = group_type
16
+ @parent = parent
17
+ end
18
+ end
19
+
20
+ def self.cartesian_product!(lval, rval)
21
+ product = []
22
+ (lval.size * rval.size).times do |index|
23
+ val = []
24
+ val.push(*lval[index % lval.size])
25
+ val.push(*rval[index % rval.size])
26
+ product << val
27
+ end
28
+ lval.replace(product)
29
+ end
30
+
31
+ def self.expand_path(parts)
32
+ if parts.is_a?(Array)
33
+ paths = [[]]
34
+
35
+ unless parts.respond_to?(:group_type)
36
+ new_parts = Group.new(:any, nil)
37
+ parts.each{|p| new_parts << p}
38
+ parts = new_parts
39
+ end
40
+
41
+ case parts.group_type
42
+ when :all
43
+ parts.each do |p|
44
+ cartesian_product!(paths, expand_path(p))
45
+ end
46
+ when :any
47
+ parts.each do |p|
48
+ cartesian_product!(paths, expand_path(p))
49
+ end
50
+ paths.unshift([])
51
+ when :one
52
+ cartesian_product!(paths, parts.collect do |p|
53
+ expand_path(p)
54
+ end)
55
+ end
56
+ paths.each{|p| p.compact!; p.flatten! }
57
+ paths
58
+ else
59
+ [[parts]]
60
+ end
61
+
62
+ end
63
+ end
64
+ end
65
+ end
@@ -2,23 +2,14 @@ class Usher
2
2
  class Route
3
3
  class Variable
4
4
  attr_reader :type, :name, :validator, :regex_matcher
5
- attr_accessor :look_ahead, :globs_capture_separators, :default_value
5
+ attr_accessor :look_ahead, :default_value
6
6
 
7
- def initialize(type, name, validator = nil, regex_matcher = nil, globs_capture_separators = false)
8
- @type = type
9
- @name = :"#{name}"
7
+ def initialize(name, regex_matcher = nil, validator = nil)
8
+ @name = name.to_s.to_sym
10
9
  @validator = validator
11
10
  @regex_matcher = regex_matcher
12
- @globs_capture_separators = globs_capture_separators
13
- end
14
-
15
- def to_s
16
- "#{type}#{name}"
17
- end
18
-
19
- def greedy?
20
- type == :'!'
21
11
  end
12
+ private :initialize
22
13
 
23
14
  def valid!(val)
24
15
  case @validator
@@ -26,7 +17,7 @@ class Usher
26
17
  begin
27
18
  @validator.call(val)
28
19
  rescue Exception => e
29
- raise ValidationException.new("#{val} does not conform to #{@validator}, root cause #{e.inspect}")
20
+ raise ValidationException.new("#{val} does not conform to #{@g}, root cause #{e.inspect}")
30
21
  end
31
22
  else
32
23
  @validator === val or raise(ValidationException.new("#{val} does not conform to #{@validator}, root cause #{e.inspect}"))
@@ -34,8 +25,24 @@ class Usher
34
25
  end
35
26
 
36
27
  def ==(o)
37
- o && (o.type == @type && o.name == @name && o.validator == @validator)
28
+ o && (o.class == self.class && o.name == @name && o.validator == @validator)
29
+ end
30
+
31
+ class Single < Variable
32
+ def to_s
33
+ ":#{name}"
34
+ end
35
+ end
36
+
37
+ class Glob < Variable
38
+ def to_s
39
+ "*#{name}"
40
+ end
38
41
  end
42
+
43
+ Greedy = Class.new(Variable)
44
+
39
45
  end
46
+
40
47
  end
41
48
  end
data/lib/usher/route.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require File.join(File.dirname(__FILE__), 'route', 'path')
2
+ require File.join(File.dirname(__FILE__), 'route', 'util')
2
3
  require File.join(File.dirname(__FILE__), 'route', 'variable')
3
4
  require File.join(File.dirname(__FILE__), 'route', 'request_method')
4
5
 
@@ -4,7 +4,7 @@ class Usher
4
4
  class Splitter
5
5
 
6
6
  def self.for_delimiters(router, valid_regex)
7
- SplitterInstance.new(Regexp.new("[#{router.delimiters.collect{|d| Regexp.quote(d)}}]|[^#{router.delimiters.collect{|d| Regexp.quote(d)}}]+"))
7
+ SplitterInstance.new(Regexp.new("[#{router.delimiters.collect{|d| Regexp.quote(d)}.join}]|[^#{router.delimiters.collect{|d| Regexp.quote(d)}.join}]+"))
8
8
  end
9
9
 
10
10
  class SplitterInstance
@@ -91,21 +91,19 @@ class Usher
91
91
  result = ''
92
92
  path.parts.each do |part|
93
93
  case part
94
- when Route::Variable
94
+ when Route::Variable::Glob
95
95
  value = (params && params.delete(part.name)) || part.default_value || raise(MissingParameterException.new)
96
- case part.type
97
- when :*
98
- value.each_with_index do |current_value, index|
99
- current_value = current_value.to_s unless current_value.is_a?(String)
100
- part.valid!(current_value)
101
- result << current_value
102
- result << '/' if index != value.size - 1
103
- end
104
- when :':'
105
- value = value.to_s unless value.is_a?(String)
106
- part.valid!(value)
107
- result << value
96
+ value.each_with_index do |current_value, index|
97
+ current_value = current_value.to_s unless current_value.is_a?(String)
98
+ part.valid!(current_value)
99
+ result << current_value
100
+ result << '/' if index != value.size - 1
108
101
  end
102
+ when Route::Variable
103
+ value = (params && params.delete(part.name)) || part.default_value || raise(MissingParameterException.new)
104
+ value = value.to_s unless value.is_a?(String)
105
+ part.valid!(value)
106
+ result << value
109
107
  else
110
108
  result << part
111
109
  end
@@ -18,16 +18,23 @@ class Usher
18
18
  @split_regex = split_regex
19
19
  end
20
20
 
21
+ def parse_and_expand(path, requirements = nil, default_values = nil)
22
+ Usher::Route::Util.expand_path(parse(path, requirements, default_values))
23
+ end
24
+
21
25
  def parse(path, requirements = nil, default_values = nil)
22
- parts = Group.new(:all, nil)
26
+ parts = Usher::Route::Util::Group.new(:all, nil)
23
27
  ss = StringScanner.new(path)
24
28
  current_group = parts
25
29
  while !ss.eos?
26
30
  part = ss.scan(@split_regex)
27
31
  case part[0]
28
- when ?*, ?:
29
- type = part.slice!(0).chr.to_sym
30
- current_group << Usher::Route::Variable.new(type, part, requirements && requirements[part.to_sym])
32
+ when ?*
33
+ var_name = part[1, part.size - 1].to_sym
34
+ current_group << Usher::Route::Variable::Glob.new(part[1, part.size - 1], nil, requirements && requirements[var_name])
35
+ when ?:
36
+ var_name = part[1, part.size - 1].to_sym
37
+ current_group << Usher::Route::Variable::Single.new(part[1, part.size - 1], nil, requirements && requirements[var_name])
31
38
  when ?{
32
39
  pattern = ''
33
40
  count = 1
@@ -46,103 +53,43 @@ class Usher
46
53
  regex = Regexp.new(pattern)
47
54
  if variable
48
55
  variable_type = variable.slice!(0).chr.to_sym
56
+ variable_class = case variable_type
57
+ when :'!' then Usher::Route::Variable::Greedy
58
+ when :* then Usher::Route::Variable::Glob
59
+ when :':' then Usher::Route::Variable::Single
60
+ end
61
+
49
62
  variable_name = variable[0, variable.size - 1].to_sym
50
- current_group << Usher::Route::Variable.new(variable_type, variable_name, requirements && requirements[variable_name], regex)
63
+ current_group << variable_class.new(variable_name, regex, requirements && requirements[variable_name])
51
64
  else
52
65
  current_group << regex
53
66
  end
54
67
  when ?(
55
- new_group = Group.new(:any, current_group)
68
+ new_group = Usher::Route::Util::Group.new(:any, current_group)
56
69
  current_group << new_group
57
70
  current_group = new_group
58
71
  when ?)
59
- current_group = current_group.parent.type == :one ? current_group.parent.parent : current_group.parent
72
+ current_group = current_group.parent.group_type == :one ? current_group.parent.parent : current_group.parent
60
73
  when ?|
61
- unless current_group.parent.type == :one
74
+ unless current_group.parent.group_type == :one
62
75
  detached_group = current_group.parent.pop
63
- new_group = Group.new(:one, detached_group.parent)
76
+ new_group = Usher::Route::Util::Group.new(:one, detached_group.parent)
64
77
  detached_group.parent = new_group
65
- detached_group.type = :all
78
+ detached_group.group_type = :all
66
79
  new_group << detached_group
67
80
  new_group.parent << new_group
68
81
  end
69
- current_group.parent << Group.new(:all, current_group.parent)
82
+ current_group.parent << Usher::Route::Util::Group.new(:all, current_group.parent)
70
83
  current_group = current_group.parent.last
71
84
  else
72
85
  current_group << part
73
86
  end
74
87
  end unless !path || path.empty?
75
- paths = calc_paths(parts)
76
- paths.each do |path|
77
- path.each_with_index do |part, index|
78
- if part.is_a?(Usher::Route::Variable)
79
- part.default_value = default_values[part.name] if default_values
80
-
81
- case part.type
82
- when :*
83
- part.look_ahead = path[index + 1, path.size].find{|p| !p.is_a?(Usher::Route::Variable) && !@router.delimiter_chars.include?(p[0])} || nil
84
- when :':'
85
- part.look_ahead = path[index + 1, path.size].find{|p| @router.delimiter_chars.include?(p[0])} || @router.delimiters.first
86
- end
87
- end
88
- end
89
- end
90
- paths
91
- end
92
-
93
- private
94
-
95
- def cartesian_product!(lval, rval)
96
- product = []
97
- (lval.size * rval.size).times do |index|
98
- val = []
99
- val.push(*lval[index % lval.size])
100
- val.push(*rval[index % rval.size])
101
- product << val
102
- end
103
- lval.replace(product)
88
+ parts
104
89
  end
105
90
 
106
- def calc_paths(parts)
107
- if parts.is_a?(Group)
108
- paths = [[]]
109
- case parts.type
110
- when :all
111
- parts.each do |p|
112
- cartesian_product!(paths, calc_paths(p))
113
- end
114
- when :any
115
- parts.each do |p|
116
- cartesian_product!(paths, calc_paths(p))
117
- end
118
- paths.unshift([])
119
- when :one
120
- cartesian_product!(paths, parts.collect do |p|
121
- calc_paths(p)
122
- end)
123
- end
124
- paths.each{|p| p.compact!; p.flatten! }
125
- paths
126
- else
127
- [[parts]]
128
- end
129
-
130
- end
131
91
  end
132
92
 
133
- class Group < Array
134
- attr_accessor :type
135
- attr_accessor :parent
136
-
137
- def inspect
138
- "#{type}->#{super}"
139
- end
140
-
141
- def initialize(type, parent)
142
- @type = type
143
- @parent = parent
144
- end
145
- end
146
93
  end
147
94
  end
148
95
  end
data/lib/usher.rb CHANGED
@@ -31,7 +31,7 @@ class Usher
31
31
  # set.reset!
32
32
  # set.empty? => true
33
33
  def reset!
34
- @tree = Node.root(self, @request_methods, @globs_capture_separators)
34
+ @tree = Node.root(self, @request_methods)
35
35
  @named_routes = {}
36
36
  @routes = []
37
37
  @route_count = 0
@@ -41,9 +41,6 @@ class Usher
41
41
 
42
42
  # Creates a route set, with options
43
43
  #
44
- # <tt>:globs_capture_separators</tt>: +true+ or +false+. (default +false+) Specifies whether glob matching will also include separators
45
- # that are matched.
46
- #
47
44
  # <tt>:delimiters</tt>: Array of Strings. (default <tt>['/', '.']</tt>). Delimiters used in path separation. Array must be single character strings.
48
45
  #
49
46
  # <tt>:valid_regex</tt>: String. (default <tt>'[0-9A-Za-z\$\-_\+!\*\',]+'</tt>). String that can be interpolated into regex to match
@@ -52,7 +49,6 @@ class Usher
52
49
  # <tt>:request_methods</tt>: Array of Symbols. (default <tt>[:protocol, :domain, :port, :query_string, :remote_ip, :user_agent, :referer, :method, :subdomains]</tt>)
53
50
  # Array of methods called against the request object for the purposes of matching route requirements.
54
51
  def initialize(options = nil)
55
- @globs_capture_separators = options && options.key?(:globs_capture_separators) ? options.delete(:globs_capture_separators) : false
56
52
  @delimiters = options && options.delete(:delimiters) || ['/', '.']
57
53
  @delimiter_chars = @delimiters.collect{|d| d[0]}
58
54
  @delimiters_regex = @delimiters.collect{|d| Regexp.quote(d)} * '|'
@@ -154,7 +150,7 @@ class Usher
154
150
  # * +requirements+ - After transformation, tests the condition using ===. If it returns false, it raises an <tt>Usher::ValidationException</tt>
155
151
  # * +conditions+ - Accepts any of the +request_methods+ specificied in the construction of Usher. This can be either a <tt>string</tt> or a regular expression.
156
152
  # * Any other key is interpreted as a requirement for the variable of its name.
157
- def add_route(path, options = nil)
153
+ def add_route(unprocessed_path, options = nil)
158
154
  conditions = options && options.delete(:conditions) || nil
159
155
  requirements = options && options.delete(:requirements) || nil
160
156
  default_values = options && options.delete(:default_values) || nil
@@ -168,10 +164,30 @@ class Usher
168
164
  end
169
165
  end
170
166
 
171
- path = parser.parse(path, requirements, default_values) if path.is_a?(String)
167
+ unprocessed_path = parser.parse(unprocessed_path, requirements, default_values) if unprocessed_path.is_a?(String)
168
+
169
+ unless unprocessed_path.first.is_a?(Route::Util::Group)
170
+ group = Usher::Route::Util::Group.new(:all, nil)
171
+ unprocessed_path.each{|p| group << p}
172
+ unprocessed_path = group
173
+ end
174
+
175
+ paths = Route::Util.expand_path(unprocessed_path)
172
176
 
177
+ paths.each do |path|
178
+ path.each_with_index do |part, index|
179
+ part.default_value = default_values[part.name] if part.is_a?(Usher::Route::Variable) && default_values && default_values[part.name]
180
+ case part
181
+ when Usher::Route::Variable::Glob
182
+ part.look_ahead = path[index + 1, path.size].find{|p| !p.is_a?(Usher::Route::Variable) && !delimiter_chars.include?(p[0])} || nil
183
+ when Usher::Route::Variable
184
+ part.look_ahead = path[index + 1, path.size].find{|p| delimiter_chars.include?(p[0])} || delimiters.first
185
+ end
186
+ end
187
+ end
188
+
173
189
  route = Route.new(
174
- path,
190
+ paths,
175
191
  self,
176
192
  conditions,
177
193
  requirements,
@@ -198,6 +214,16 @@ class Usher
198
214
  @tree.find(self, request, path, @splitter.url_split(path))
199
215
  end
200
216
 
217
+ # Recognizes a +path+ and returns +nil+ or an Usher::Node::Response, which is a struct containing a Usher::Route::Path and an array of arrays containing the extracted parameters. Convenience method for when recognizing on the request object is unneeded.
218
+ #
219
+ # Request = Struct.new(:path)
220
+ # set = Usher.new
221
+ # route = set.add_route('/test')
222
+ # set.recognize_path('/test').path.route == route => true
223
+ def recognize_path(path)
224
+ recognize(nil, path)
225
+ end
226
+
201
227
  # Recognizes a set of +parameters+ and gets the closest matching Usher::Route::Path or +nil+ if no route exists.
202
228
  #
203
229
  # set = Usher.new
@@ -1,7 +1,5 @@
1
1
  require 'lib/usher'
2
2
 
3
-
4
-
5
3
  def build_email_mock(email)
6
4
  request = mock "Request"
7
5
  request.should_receive(:email).any_number_of_times.and_return(email)
@@ -24,14 +22,14 @@ describe "Usher (for email) route recognition" do
24
22
  it "should recognize a wildcard domain" do
25
23
  receiver = mock('receiver')
26
24
  receiver.should_receive(:action).with({:domain => 'gmail.com'}).exactly(1)
27
- @route_set.for('joshbuddy@*domain') { |params| receiver.action(params) }
25
+ @route_set.for('joshbuddy@{!domain,.*}') { |params| receiver.action(params) }
28
26
  @route_set.act('joshbuddy@gmail.com')
29
27
  end
30
28
 
31
29
  it "should recognize a complex email" do
32
30
  receiver = mock('receiver')
33
31
  receiver.should_receive(:action).with({:subject => 'sub+ect', :id => '123', :sid => '456', :tok => 'sdqwe123ae', :domain => 'mydomain.org'}).exactly(1)
34
- @route_set.for(':subject.{:id,^\d+$}-{:sid,^\d+$}-{:tok,^\w+$}@*domain') { |params| receiver.action(params) }
32
+ @route_set.for(':subject.{!id,\d+}-{!sid,\d+}-{!tok,\w+}@{!domain,.*}') { |params| receiver.action(params) }
35
33
  @route_set.act('sub+ect.123-456-sdqwe123ae@mydomain.org')
36
34
  end
37
35
 
@@ -4,41 +4,41 @@ describe "Usher route tokenizing" do
4
4
 
5
5
 
6
6
  it "should split / delimited routes" do
7
- Usher.new(:delimiters => ['/', '.'], :valid_regex => '[0-9A-Za-z\$\-_\+!\*\',]+').parser.parse('/test/this/split').should == [['/', 'test', '/','this', '/', 'split']]
7
+ Usher.new(:delimiters => ['/', '.'], :valid_regex => '[0-9A-Za-z\$\-_\+!\*\',]+').parser.parse_and_expand('/test/this/split').should == [['/', 'test', '/','this', '/', 'split']]
8
8
  end
9
9
 
10
10
  it "should split / delimited routes with a regex in it" do
11
- Usher.new(:delimiters => ['/', '.'], :valid_regex => '[0-9A-Za-z\$\-_\+!\*\',]+').parser.parse('/test/{this}/split').should == [['/', 'test', '/', /this/, '/', 'split']]
11
+ Usher.new(:delimiters => ['/', '.'], :valid_regex => '[0-9A-Za-z\$\-_\+!\*\',]+').parser.parse_and_expand('/test/{this}/split').should == [['/', 'test', '/', /this/, '/', 'split']]
12
12
  end
13
13
 
14
14
  it "should split on ' ' delimited routes as well" do
15
- Usher.new(:delimiters => [' '], :valid_regex => '[0-9A-Za-z\$\-_\+!\*\',]+').parser.parse('test this split').should == [['test', ' ', 'this', ' ', 'split']]
15
+ Usher.new(:delimiters => [' '], :valid_regex => '[0-9A-Za-z\$\-_\+!\*\',]+').parser.parse_and_expand('test this split').should == [['test', ' ', 'this', ' ', 'split']]
16
16
  end
17
17
 
18
18
  it "should split on email delimiters as well" do
19
- Usher.new(:delimiters => ['@', '+', '-', '.'], :valid_regex => '[a-zA-Z0-9]+').parser.parse('one+more.12345-09876-alphanum3ric5@domain.com').should == [["one", '+', "more", ".", "12345", '-', "09876", '-', "alphanum3ric5", "@", "domain", ".", "com"]]
19
+ Usher.new(:delimiters => ['@', '+', '-', '.'], :valid_regex => '[a-zA-Z0-9]+').parser.parse_and_expand('one+more.12345-09876-alphanum3ric5@domain.com').should == [["one", '+', "more", ".", "12345", '-', "09876", '-', "alphanum3ric5", "@", "domain", ".", "com"]]
20
20
  end
21
21
 
22
22
  it "should split on ' ' delimited routes for more complex routes as well" do
23
- Usher.new(:delimiters => [' '], :valid_regex => '[0-9A-Za-z\$\-_\+!\*\',]+').parser.parse('(test|this) split').should == [['test', ' ', 'split'], ['this', ' ', 'split']]
23
+ Usher.new(:delimiters => [' '], :valid_regex => '[0-9A-Za-z\$\-_\+!\*\',]+').parser.parse_and_expand('(test|this) split').should == [['test', ' ', 'split'], ['this', ' ', 'split']]
24
24
  end
25
25
 
26
26
  it "should group optional parts with brackets" do
27
- Usher.new(:delimiters => ['/', '.'], :valid_regex => '[0-9A-Za-z\$\-_\+!\*\',]+').parser.parse('/test/this(/split)').should == [
27
+ Usher.new(:delimiters => ['/', '.'], :valid_regex => '[0-9A-Za-z\$\-_\+!\*\',]+').parser.parse_and_expand('/test/this(/split)').should == [
28
28
  ['/', 'test', '/', 'this'],
29
29
  ['/', 'test', '/', 'this', '/', 'split']
30
30
  ]
31
31
  end
32
32
 
33
33
  it "should group exclusive optional parts with brackets and pipes" do
34
- Usher.new(:delimiters => ['/', '.'], :valid_regex => '[0-9A-Za-z\$\-_\+!\*\',]+').parser.parse('/test/this(/split|/split2)').should == [
34
+ Usher.new(:delimiters => ['/', '.'], :valid_regex => '[0-9A-Za-z\$\-_\+!\*\',]+').parser.parse_and_expand('/test/this(/split|/split2)').should == [
35
35
  ['/', 'test', '/', 'this','/', 'split'],
36
36
  ['/', 'test', '/', 'this','/', 'split2']
37
37
  ]
38
38
  end
39
39
 
40
40
  it "should group exclusive optional-optional parts with brackets and pipes" do
41
- Usher.new(:delimiters => ['/', '.'], :valid_regex => '[0-9A-Za-z\$\-_\+!\*\',]+').parser.parse('/test/this((/split|/split2))').should == [
41
+ Usher.new(:delimiters => ['/', '.'], :valid_regex => '[0-9A-Za-z\$\-_\+!\*\',]+').parser.parse_and_expand('/test/this((/split|/split2))').should == [
42
42
  ['/', 'test','/', 'this'],
43
43
  ['/', 'test','/', 'this', '/', 'split'],
44
44
  ['/', 'test','/', 'this', '/', 'split2']
@@ -46,7 +46,7 @@ describe "Usher route tokenizing" do
46
46
  end
47
47
 
48
48
  it "should group optional parts with brackets (for non overlapping groups)" do
49
- Usher.new(:delimiters => ['/', '.'], :valid_regex => '[0-9A-Za-z\$\-_\+!\*\',]+').parser.parse('/test/this(/split)(/split2)') == [
49
+ Usher.new(:delimiters => ['/', '.'], :valid_regex => '[0-9A-Za-z\$\-_\+!\*\',]+').parser.parse_and_expand('/test/this(/split)(/split2)') == [
50
50
  ['/', "test", '/', "this"],
51
51
  ['/', "test", '/', "this", '/', "split"],
52
52
  ['/', "test", '/', "this", '/', "split2"],
@@ -55,20 +55,20 @@ describe "Usher route tokenizing" do
55
55
  end
56
56
 
57
57
  it "should group nested-optional parts with brackets" do
58
- Usher.new(:delimiters => ['/', '.'], :valid_regex => '[0-9A-Za-z\$\-_\+!\*\',]+').parser.parse('/test/this(/split(.:format))') == [
58
+ Usher.new(:delimiters => ['/', '.'], :valid_regex => '[0-9A-Za-z\$\-_\+!\*\',]+').parser.parse_and_expand('/test/this(/split(.:format))') == [
59
59
  ['/', "test", '/', "this"],
60
60
  ['/', "test", '/', "this", '/', "split"],
61
- ['/', "test", '/', "this", '/', "split", '.', Usher::Route::Variable.new(:':', :format)]
61
+ ['/', "test", '/', "this", '/', "split", '.', Usher::Route::Variable::Single.new(:format)]
62
62
  ]
63
63
  end
64
64
 
65
65
  it "should to_s all different variable types" do
66
- Usher.new(:delimiters => ['/', '.'], :valid_regex => '[0-9A-Za-z\$\-_\+!\*\',]+').parser.parse('/:split/*splitter').first.collect{|v| v.to_s} ==
66
+ Usher.new(:delimiters => ['/', '.'], :valid_regex => '[0-9A-Za-z\$\-_\+!\*\',]+').parser.parse_and_expand('/:split/*splitter').first.collect{|v| v.to_s} ==
67
67
  [ ':split', '*splitter' ]
68
68
  end
69
69
 
70
70
  it "should == variable types" do
71
- parts = Usher.new(:delimiters => ['/', '.'], :valid_regex => '[0-9A-Za-z\$\-_\+!\*\',]+').parser.parse('/:split/:split').first
71
+ parts = Usher.new(:delimiters => ['/', '.'], :valid_regex => '[0-9A-Za-z\$\-_\+!\*\',]+').parser.parse_and_expand('/:split/:split').first
72
72
  parts[1].should == parts[3]
73
73
  end
74
74
 
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.11
4
+ version: 0.5.1
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-07-22 00:00:00 -07:00
12
+ date: 2009-08-22 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -20,7 +20,7 @@ dependencies:
20
20
  requirements:
21
21
  - - ">="
22
22
  - !ruby/object:Gem::Version
23
- version: 0.0.5
23
+ version: 0.0.6
24
24
  version:
25
25
  description: A general purpose routing library
26
26
  email: joshbuddy@gmail.com
@@ -52,6 +52,7 @@ files:
52
52
  - lib/usher/route.rb
53
53
  - lib/usher/route/path.rb
54
54
  - lib/usher/route/request_method.rb
55
+ - lib/usher/route/util.rb
55
56
  - lib/usher/route/variable.rb
56
57
  - lib/usher/splitter.rb
57
58
  - lib/usher/util.rb
@@ -77,6 +78,7 @@ files:
77
78
  - spec/spec.opts
78
79
  has_rdoc: false
79
80
  homepage: http://github.com/joshbuddy/usher
81
+ licenses:
80
82
  post_install_message:
81
83
  rdoc_options:
82
84
  - --charset=UTF-8
@@ -97,7 +99,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
97
99
  requirements: []
98
100
 
99
101
  rubyforge_project:
100
- rubygems_version: 1.2.0
102
+ rubygems_version: 1.3.5
101
103
  signing_key:
102
104
  specification_version: 3
103
105
  summary: A general purpose routing library