joshbuddy-usher 0.4.8 → 0.4.10

Sign up to get free protection for your applications and to get access to all the features.
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.3'
12
+ s.add_dependency 'fuzzyhash', '>=0.0.5'
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
- :minor: 4
3
- :patch: 8
2
+ :patch: 10
4
3
  :major: 0
4
+ :minor: 4
@@ -8,7 +8,7 @@ class Usher
8
8
 
9
9
  def initialize(&blk)
10
10
  @routes = Usher.new(:request_methods => [:method, :host, :port, :scheme])
11
- @generator = Usher::Generators::URL.new(@routes)
11
+ @generator = Usher::Util::Generators::URL.new(@routes)
12
12
  instance_eval(&blk) if blk
13
13
  end
14
14
 
@@ -13,7 +13,7 @@ class Usher
13
13
 
14
14
  def reset!
15
15
  @usher ||= Usher.new
16
- @url_generator ||= Usher::Generators::URL.new(@usher)
16
+ @url_generator ||= Usher::Util::Generators::URL.new(@usher)
17
17
  @module ||= Module.new
18
18
  @module.instance_methods.each do |selector|
19
19
  @module.class_eval { remove_method selector }
@@ -73,7 +73,7 @@ class Usher
73
73
 
74
74
  def reset!
75
75
  @router = Usher.new
76
- @url_generator = Usher::Generators::URL.new(@router)
76
+ @url_generator = Usher::Util::Generators::URL.new(@router)
77
77
  @configuration_files = []
78
78
  @module ||= Module.new
79
79
  @controller_route_added = false
data/lib/usher/node.rb CHANGED
@@ -6,13 +6,14 @@ class Usher
6
6
 
7
7
  Response = Struct.new(:path, :params)
8
8
 
9
- attr_reader :lookup
9
+ attr_reader :lookup, :greedy_lookup
10
10
  attr_accessor :terminates, :exclusive_type, :parent, :value, :request_methods, :globs_capture_separators
11
11
 
12
12
  def initialize(parent, value)
13
13
  @parent = parent
14
14
  @value = value
15
15
  @lookup = Hash.new
16
+ @greedy_lookup = Hash.new
16
17
  @exclusive_type = nil
17
18
  end
18
19
 
@@ -20,10 +21,18 @@ class Usher
20
21
  @lookup = FuzzyHash.new(@lookup)
21
22
  end
22
23
 
24
+ def upgrade_greedy_lookup
25
+ @greedy_lookup = FuzzyHash.new(@greedy_lookup)
26
+ end
27
+
23
28
  def depth
24
29
  @depth ||= @parent && @parent.is_a?(Node) ? @parent.depth + 1 : 0
25
30
  end
26
31
 
32
+ def greedy?
33
+ !@greedy_lookup.empty?
34
+ end
35
+
27
36
  def self.root(route_set, request_methods, globs_capture_separators)
28
37
  root = self.new(route_set, nil)
29
38
  root.request_methods = request_methods
@@ -69,16 +78,29 @@ class Usher
69
78
  end
70
79
  else
71
80
  key.globs_capture_separators = globs_capture_separators if key.is_a?(Route::Variable)
72
-
73
- if !key.is_a?(Route::Variable)
81
+
82
+ case key
83
+ 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
91
+ 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
98
+ end
99
+ else
74
100
  current_node.upgrade_lookup if key.is_a?(Regexp)
75
101
  current_node.lookup[key] ||= Node.new(current_node, key)
76
- elsif key.regex_matcher
77
- current_node.upgrade_lookup
78
- current_node.lookup[key.regex_matcher] ||= Node.new(current_node, key)
79
- else
80
- current_node.lookup[nil] ||= Node.new(current_node, key)
81
- end
102
+ end
103
+
82
104
  end
83
105
  current_node = target_node
84
106
  end
@@ -87,29 +109,38 @@ class Usher
87
109
  route
88
110
  end
89
111
 
90
- def find(usher, request, path, params = [])
112
+ def find(usher, request, original_path, path, params = [], position = 0)
91
113
  if exclusive_type
92
114
  [lookup[request.send(exclusive_type)], lookup[nil]].each do |n|
93
- if n && (ret = n.find(usher, request, path.dup, params.dup))
115
+ if n && (ret = n.find(usher, request, original_path, path.dup, params.dup, position))
94
116
  return ret
95
117
  end
96
118
  end
97
119
  elsif path.size.zero? && terminates?
98
120
  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])))
122
+ position += matched_part.size
123
+ params << [next_path.value.name, whole_path.slice!(0, matched_part.size)]
124
+ next_path.find(usher, request, original_path, whole_path.size.zero? ? whole_path : usher.splitter.url_split(whole_path), params, position)
99
125
  elsif !path.size.zero? && (next_part = lookup[part = path.shift] || lookup[nil])
126
+ position += part.size
100
127
  case next_part.value
101
128
  when Route::Variable
102
129
  case next_part.value.type
103
130
  when :*
104
131
  params << [next_part.value.name, []] unless params.last && params.last.first == next_part.value.name
105
132
  loop do
106
- if (next_part.value.look_ahead === part || (!usher.splitter.delimiter_chars.include?(part[0]) && next_part.value.regex_matcher && !next_part.value.regex_matcher.match(part)))
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)))
107
134
  path.unshift(part)
108
- path.unshift(next_part.parent.value) if usher.splitter.delimiter_chars.include?(next_part.parent.value[0])
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
109
140
  break
110
141
  elsif next_part.value.globs_capture_separators
111
142
  params.last.last << part
112
- elsif !usher.splitter.delimiter_chars.include?(part[0])
143
+ elsif !usher.delimiter_chars.include?(part[0])
113
144
  next_part.value.valid!(part)
114
145
  params.last.last << part
115
146
  end
@@ -124,11 +155,13 @@ class Usher
124
155
  var.valid!(part)
125
156
  params << [var.name, part]
126
157
  until (var.look_ahead === path.first) || path.empty?
127
- params.last.last << path.shift
158
+ next_path_part = path.shift
159
+ position += next_path_part.size
160
+ params.last.last << next_path_part
128
161
  end
129
162
  end
130
163
  end
131
- next_part.find(usher, request, path, params)
164
+ next_part.find(usher, request, original_path, path, params, position)
132
165
  else
133
166
  nil
134
167
  end
@@ -16,6 +16,10 @@ class Usher
16
16
  "#{type}#{name}"
17
17
  end
18
18
 
19
+ def greedy?
20
+ type == :'!'
21
+ end
22
+
19
23
  def valid!(val)
20
24
  case @validator
21
25
  when Proc
data/lib/usher/route.rb CHANGED
@@ -4,18 +4,17 @@ require File.join(File.dirname(__FILE__), 'route', 'request_method')
4
4
 
5
5
  class Usher
6
6
  class Route
7
- attr_reader :paths, :original_path, :requirements, :conditions, :destination, :named, :generate_with
7
+ attr_reader :paths, :requirements, :conditions, :destination, :named, :generate_with
8
8
 
9
9
  GenerateWith = Struct.new(:scheme, :port, :host)
10
10
 
11
- def initialize(original_path, router, conditions, requirements, default_values, generate_with) # :nodoc:
12
- @original_path = original_path
11
+ def initialize(parsed_paths, router, conditions, requirements, default_values, generate_with) # :nodoc:
13
12
  @router = router
14
13
  @requirements = requirements
15
14
  @conditions = conditions
16
15
  @default_values = default_values
17
- @paths = @router.splitter.split(@original_path, @requirements, @default_values).collect {|path| Path.new(self, path)}
18
16
  @generate_with = GenerateWith.new(generate_with[:scheme], generate_with[:port], generate_with[:host]) if generate_with
17
+ @paths = parsed_paths.collect {|path| Path.new(self, path)}
19
18
  end
20
19
 
21
20
  def grapher
@@ -3,156 +3,19 @@ require 'strscan'
3
3
  class Usher
4
4
  class Splitter
5
5
 
6
- def self.for_delimiters(delimiters, valid_regex)
7
- delimiters_regex = delimiters.collect{|d| Regexp.quote(d)} * '|'
8
- SplitterInstance.new(
9
- delimiters,
10
- Regexp.new('((:|\*)?' + valid_regex + '|' + delimiters_regex + '|\(|\)|\||\{)'),
11
- Regexp.new("[#{delimiters.collect{|d| Regexp.quote(d)}}]|[^#{delimiters.collect{|d| Regexp.quote(d)}}]+")
12
- )
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)}}]+"))
13
8
  end
14
-
15
- attr_reader :paths
16
9
 
17
10
  class SplitterInstance
18
-
19
- attr_reader :delimiter_chars
20
-
21
- def initialize(delimiters, split_regex, url_split_regex)
22
- @delimiters = delimiters
23
- @delimiter_chars = delimiters.collect{|d| d[0]}
24
- @split_regex = split_regex
11
+
12
+ def initialize(url_split_regex)
25
13
  @url_split_regex = url_split_regex
26
14
  end
27
15
 
28
16
  def url_split(path)
29
17
  path.scan(@url_split_regex)
30
18
  end
31
-
32
- def split(path, requirements = nil, default_values = nil)
33
- parts = Group.new(:all, nil)
34
- ss = StringScanner.new(path)
35
- current_group = parts
36
- while !ss.eos?
37
- part = ss.scan(@split_regex)
38
- case part[0]
39
- when ?*, ?:
40
- type = part.slice!(0).chr.to_sym
41
- current_group << Usher::Route::Variable.new(type, part, requirements && requirements[part.to_sym])
42
- when ?{
43
- pattern = ''
44
- count = 1
45
- variable = ss.scan(/[:\*]([^,]+),/)
46
- until count.zero?
47
- regex_part = ss.scan(/\{|\}|[^\{\}]+/)
48
- case regex_part[0]
49
- when ?{
50
- count += 1
51
- when ?}
52
- count -= 1
53
- end
54
- pattern << regex_part
55
- end
56
- pattern.slice!(pattern.length - 1)
57
- regex = Regexp.new(pattern)
58
- if variable
59
- variable_type = variable.slice!(0).chr.to_sym
60
- variable_name = variable[0, variable.size - 1].to_sym
61
- current_group << Usher::Route::Variable.new(variable_type, variable_name, requirements && requirements[variable_name], regex)
62
- else
63
- current_group << regex
64
- end
65
- when ?(
66
- new_group = Group.new(:any, current_group)
67
- current_group << new_group
68
- current_group = new_group
69
- when ?)
70
- current_group = current_group.parent.type == :one ? current_group.parent.parent : current_group.parent
71
- when ?|
72
- unless current_group.parent.type == :one
73
- detached_group = current_group.parent.pop
74
- new_group = Group.new(:one, detached_group.parent)
75
- detached_group.parent = new_group
76
- detached_group.type = :all
77
- new_group << detached_group
78
- new_group.parent << new_group
79
- end
80
- current_group.parent << Group.new(:all, current_group.parent)
81
- current_group = current_group.parent.last
82
- else
83
- current_group << part
84
- end
85
- end unless !path || path.empty?
86
- paths = calc_paths(parts)
87
- paths.each do |path|
88
- path.each_with_index do |part, index|
89
- if part.is_a?(Usher::Route::Variable)
90
- part.default_value = default_values[part.name] if default_values
91
-
92
- case part.type
93
- when :*
94
- part.look_ahead = path[index + 1, path.size].find{|p| !p.is_a?(Usher::Route::Variable) && !@delimiter_chars.include?(p[0])} || nil
95
- when :':'
96
- part.look_ahead = path[index + 1, path.size].find{|p| @delimiter_chars.include?(p[0])} || @delimiters.first
97
- end
98
- end
99
- end
100
- end
101
- paths
102
- end
103
-
104
- private
105
-
106
- def cartesian_product!(lval, rval)
107
- product = []
108
- (lval.size * rval.size).times do |index|
109
- val = []
110
- val.push(*lval[index % lval.size])
111
- val.push(*rval[index % rval.size])
112
- product << val
113
- end
114
- lval.replace(product)
115
- end
116
-
117
- def calc_paths(parts)
118
- if parts.is_a?(Group)
119
- paths = [[]]
120
- case parts.type
121
- when :all
122
- parts.each do |p|
123
- cartesian_product!(paths, calc_paths(p))
124
- end
125
- when :any
126
- parts.each do |p|
127
- cartesian_product!(paths, calc_paths(p))
128
- end
129
- paths.unshift([])
130
- when :one
131
- cartesian_product!(paths, parts.collect do |p|
132
- calc_paths(p)
133
- end)
134
- end
135
- paths.each{|p| p.compact!; p.flatten! }
136
- paths
137
- else
138
- [[parts]]
139
- end
140
-
141
- end
142
- end
143
-
144
- class Group < Array
145
- attr_accessor :type
146
- attr_accessor :parent
147
-
148
- def inspect
149
- "#{type}->#{super}"
150
- end
151
-
152
- def initialize(type, parent)
153
- @type = type
154
- @parent = parent
155
- end
156
19
  end
157
20
 
158
21
  end
@@ -0,0 +1,133 @@
1
+ require 'rack'
2
+
3
+ unless Rack::Utils.respond_to?(:uri_escape)
4
+ module Rack
5
+
6
+ module Utils
7
+
8
+ def uri_escape(s)
9
+ s.to_s.gsub(/([^:\/?\[\]\-_~\.!\$&'\(\)\*\+,;=@a-zA-Z0-9]+)/n) {
10
+ '%'<<$1.unpack('H2'*$1.size).join('%').upcase
11
+ }.tr(' ', '+')
12
+ end
13
+ module_function :uri_escape
14
+
15
+ def uri_unescape(s)
16
+ gsub(/((?:%[0-9a-fA-F]{2})+)/n){
17
+ [$1.delete('%')].pack('H*')
18
+ }
19
+ end
20
+ module_function :uri_unescape
21
+
22
+ end
23
+ end
24
+ end
25
+
26
+ class Usher
27
+ module Util
28
+ class Generators
29
+
30
+ class URL
31
+
32
+ def initialize(usher)
33
+ @usher = usher
34
+ end
35
+
36
+ def generate_full(routing_lookup, request, params = nil)
37
+ path = path_for_routing_lookup(routing_lookup, params)
38
+ result = generate_start(path, request)
39
+ result << generate_path(path, params)
40
+ end
41
+
42
+ def generate(routing_lookup, params = nil)
43
+ generate_path(path_for_routing_lookup(routing_lookup, params), params)
44
+ end
45
+
46
+ def generate_start(path, request)
47
+ result = (path.route.generate_with && path.route.generate_with.scheme || request.scheme).dup
48
+ result << '://'
49
+ result << (path.route.generate_with && path.route.generate_with.host) ? path.route.generate_with.host : request.host
50
+ port = path.route.generate_with && path.route.generate_with.port || request.port
51
+ if result[4] == ?s
52
+ result << ':' << port.to_s if port != 443
53
+ else
54
+ result << ':' << port.to_s if port != 80
55
+ end
56
+ result
57
+ end
58
+
59
+ def path_for_routing_lookup(routing_lookup, params)
60
+ path = case routing_lookup
61
+ when Symbol
62
+ route = @usher.named_routes[routing_lookup]
63
+ params.is_a?(Hash) ? route.find_matching_path(params) : route.paths.first
64
+ when Route
65
+ params.is_a?(Hash) ? routing_lookup.find_matching_path(params) : routing_lookup.paths.first
66
+ when nil
67
+ params.is_a?(Hash) ? @usher.path_for_options(params) : raise
68
+ when Route::Path
69
+ routing_lookup
70
+ end
71
+ end
72
+
73
+ # Generates a completed URL based on a +route+ or set of optional +params+
74
+ #
75
+ # set = Usher.new
76
+ # route = set.add_named_route(:test_route, '/:controller/:action')
77
+ # set.generate_url(nil, {:controller => 'c', :action => 'a'}) == '/c/a' => true
78
+ # set.generate_url(:test_route, {:controller => 'c', :action => 'a'}) == '/c/a' => true
79
+ # set.generate_url(route.primary_path, {:controller => 'c', :action => 'a'}) == '/c/a' => true
80
+ def generate_path(path, params = nil)
81
+ raise UnrecognizedException.new unless path
82
+
83
+ params = Array(params) if params.is_a?(String)
84
+ if params.is_a?(Array)
85
+ given_size = params.size
86
+ extra_params = params.last.is_a?(Hash) ? params.pop : nil
87
+ params = Hash[*path.dynamic_parts.inject([]){|a, dynamic_part| a.concat([dynamic_part.name, params.shift || raise(MissingParameterException.new("got #{given_size}, expected #{path.dynamic_parts.size} parameters"))]); a}]
88
+ params.merge!(extra_params) if extra_params
89
+ end
90
+
91
+ result = ''
92
+ path.parts.each do |part|
93
+ case part
94
+ when Route::Variable
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
108
+ end
109
+ else
110
+ result << part
111
+ end
112
+ end
113
+ result = Rack::Utils.uri_escape(result)
114
+
115
+ if params && !params.empty?
116
+ has_query = result[??]
117
+ params.each do |k,v|
118
+ case v
119
+ when Array
120
+ v.each do |v_part|
121
+ result << (has_query ? '&' : has_query = true && '?') << Rack::Utils.escape("#{k.to_s}[]") << '=' << Rack::Utils.escape(v_part.to_s)
122
+ end
123
+ else
124
+ result << (has_query ? '&' : has_query = true && '?') << Rack::Utils.escape(k.to_s) << '=' << Rack::Utils.escape(v.to_s)
125
+ end
126
+ end
127
+ end
128
+ result
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,148 @@
1
+ require 'strscan'
2
+
3
+ class Usher
4
+ module Util
5
+ class Parser
6
+
7
+ def self.for_delimiters(router, valid_regex)
8
+ ParserInstance.new(
9
+ router,
10
+ Regexp.new('((:|\*)?' + valid_regex + '|' + router.delimiters_regex + '|\(|\)|\||\{)')
11
+ )
12
+ end
13
+
14
+ class ParserInstance
15
+
16
+ def initialize(router, split_regex)
17
+ @router = router
18
+ @split_regex = split_regex
19
+ end
20
+
21
+ def parse(path, requirements = nil, default_values = nil)
22
+ parts = Group.new(:all, nil)
23
+ ss = StringScanner.new(path)
24
+ current_group = parts
25
+ while !ss.eos?
26
+ part = ss.scan(@split_regex)
27
+ 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])
31
+ when ?{
32
+ pattern = ''
33
+ count = 1
34
+ variable = ss.scan(/[!:\*]([^,]+),/)
35
+ until count.zero?
36
+ regex_part = ss.scan(/\{|\}|[^\{\}]+/)
37
+ case regex_part[0]
38
+ when ?{
39
+ count += 1
40
+ when ?}
41
+ count -= 1
42
+ end
43
+ pattern << regex_part
44
+ end
45
+ pattern.slice!(pattern.length - 1)
46
+ regex = Regexp.new(pattern)
47
+ if variable
48
+ variable_type = variable.slice!(0).chr.to_sym
49
+ 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)
51
+ else
52
+ current_group << regex
53
+ end
54
+ when ?(
55
+ new_group = Group.new(:any, current_group)
56
+ current_group << new_group
57
+ current_group = new_group
58
+ when ?)
59
+ current_group = current_group.parent.type == :one ? current_group.parent.parent : current_group.parent
60
+ when ?|
61
+ unless current_group.parent.type == :one
62
+ detached_group = current_group.parent.pop
63
+ new_group = Group.new(:one, detached_group.parent)
64
+ detached_group.parent = new_group
65
+ detached_group.type = :all
66
+ new_group << detached_group
67
+ new_group.parent << new_group
68
+ end
69
+ current_group.parent << Group.new(:all, current_group.parent)
70
+ current_group = current_group.parent.last
71
+ else
72
+ current_group << part
73
+ end
74
+ 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)
104
+ end
105
+
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
+ end
132
+
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
+ end
147
+ end
148
+ end
data/lib/usher/util.rb ADDED
@@ -0,0 +1,6 @@
1
+ class Usher
2
+ module Util
3
+ autoload :Generators, File.join(File.dirname(__FILE__), 'util', 'generate')
4
+ autoload :Parser, File.join(File.dirname(__FILE__), 'util', 'parser')
5
+ end
6
+ end
data/lib/usher.rb CHANGED
@@ -4,12 +4,12 @@ require File.join(File.dirname(__FILE__), 'usher', 'grapher')
4
4
  require File.join(File.dirname(__FILE__), 'usher', 'interface')
5
5
  require File.join(File.dirname(__FILE__), 'usher', 'splitter')
6
6
  require File.join(File.dirname(__FILE__), 'usher', 'exceptions')
7
+ require File.join(File.dirname(__FILE__), 'usher', 'util')
7
8
 
8
9
  class Usher
9
10
 
10
- autoload :Generators, File.join(File.dirname(__FILE__), 'usher', 'generate')
11
11
 
12
- attr_reader :tree, :named_routes, :route_count, :routes, :splitter, :delimiters
12
+ attr_reader :tree, :named_routes, :route_count, :routes, :splitter, :delimiters, :delimiter_chars, :delimiters_regex
13
13
 
14
14
  SymbolArraySorter = proc {|a,b| a.hash <=> b.hash} #:nodoc:
15
15
 
@@ -54,12 +54,18 @@ class Usher
54
54
  def initialize(options = nil)
55
55
  @globs_capture_separators = options && options.key?(:globs_capture_separators) ? options.delete(:globs_capture_separators) : false
56
56
  @delimiters = options && options.delete(:delimiters) || ['/', '.']
57
+ @delimiter_chars = @delimiters.collect{|d| d[0]}
58
+ @delimiters_regex = @delimiters.collect{|d| Regexp.quote(d)} * '|'
57
59
  @valid_regex = options && options.delete(:valid_regex) || '[0-9A-Za-z\$\-_\+!\*\',]+'
58
60
  @request_methods = options && options.delete(:request_methods) || [:protocol, :domain, :port, :query_string, :remote_ip, :user_agent, :referer, :method, :subdomains]
59
- @splitter = Splitter.for_delimiters(@delimiters, @valid_regex)
61
+ @splitter = Splitter.for_delimiters(self, @valid_regex)
60
62
  reset!
61
63
  end
62
64
 
65
+ def parser
66
+ @parser ||= Util::Parser.for_delimiters(self, @valid_regex)
67
+ end
68
+
63
69
  # Adds a route referencable by +name+. Sett add_route for format +path+ and +options+.
64
70
  #
65
71
  # set = Usher.new
@@ -152,7 +158,18 @@ class Usher
152
158
  end
153
159
  end
154
160
  end
155
- route = Route.new(path, self, conditions, requirements, default_values, generate_with)
161
+
162
+ path = parser.parse(path, requirements, default_values) if path.is_a?(String)
163
+
164
+ route = Route.new(
165
+ path,
166
+ self,
167
+ conditions,
168
+ requirements,
169
+ default_values,
170
+ generate_with
171
+ )
172
+
156
173
  route.to(options) if options && !options.empty?
157
174
 
158
175
  @tree.add(route)
@@ -169,7 +186,7 @@ class Usher
169
186
  # route = set.add_route('/test')
170
187
  # set.recognize(Request.new('/test')).path.route == route => true
171
188
  def recognize(request, path = request.path)
172
- @tree.find(self, request, @splitter.url_split(path))
189
+ @tree.find(self, request, path, @splitter.url_split(path))
173
190
  end
174
191
 
175
192
  # Recognizes a set of +parameters+ and gets the closest matching Usher::Route::Path or +nil+ if no route exists.
@@ -6,7 +6,7 @@ describe "Usher URL generation" do
6
6
  before(:each) do
7
7
  @route_set = Usher.new
8
8
  @route_set.reset!
9
- @url_generator = Usher::Generators::URL.new(@route_set)
9
+ @url_generator = Usher::Util::Generators::URL.new(@route_set)
10
10
  end
11
11
 
12
12
  it "should generate a simple URL" do
@@ -6,7 +6,7 @@ describe "Usher grapher" do
6
6
  before(:each) do
7
7
  @route_set = Usher.new
8
8
  @route_set.reset!
9
- @url_generator = Usher::Generators::URL.new(@route_set)
9
+ @url_generator = Usher::Util::Generators::URL.new(@route_set)
10
10
  end
11
11
 
12
12
  it "should find a simple path" do
@@ -0,0 +1,75 @@
1
+ require 'lib/usher'
2
+
3
+ describe "Usher route tokenizing" do
4
+
5
+
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']]
8
+ end
9
+
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']]
12
+ end
13
+
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']]
16
+ end
17
+
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"]]
20
+ end
21
+
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']]
24
+ end
25
+
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 == [
28
+ ['/', 'test', '/', 'this'],
29
+ ['/', 'test', '/', 'this', '/', 'split']
30
+ ]
31
+ end
32
+
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 == [
35
+ ['/', 'test', '/', 'this','/', 'split'],
36
+ ['/', 'test', '/', 'this','/', 'split2']
37
+ ]
38
+ end
39
+
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 == [
42
+ ['/', 'test','/', 'this'],
43
+ ['/', 'test','/', 'this', '/', 'split'],
44
+ ['/', 'test','/', 'this', '/', 'split2']
45
+ ]
46
+ end
47
+
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)') == [
50
+ ['/', "test", '/', "this"],
51
+ ['/', "test", '/', "this", '/', "split"],
52
+ ['/', "test", '/', "this", '/', "split2"],
53
+ ['/', "test", '/', "this", '/', "split", '/', "split2"]
54
+ ]
55
+ end
56
+
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))') == [
59
+ ['/', "test", '/', "this"],
60
+ ['/', "test", '/', "this", '/', "split"],
61
+ ['/', "test", '/', "this", '/', "split", '.', Usher::Route::Variable.new(:':', :format)]
62
+ ]
63
+ end
64
+
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} ==
67
+ [ ':split', '*splitter' ]
68
+ end
69
+
70
+ it "should == variable types" do
71
+ parts = Usher.new(:delimiters => ['/', '.'], :valid_regex => '[0-9A-Za-z\$\-_\+!\*\',]+').parser.parse('/:split/:split').first
72
+ parts[1].should == parts[3]
73
+ end
74
+
75
+ end
@@ -93,6 +93,18 @@ describe "Usher route recognition" do
93
93
  route_set.recognize(build_request({:method => 'get', :path => '/test/part/hello/again/123/hello/again/onemore'})).params.should == [[:test, ['hello', 'again', '123', 'hello', 'again']], [:party, 'onemore']]
94
94
  end
95
95
 
96
+ it "should recgonize a greedy regex single variable" do
97
+ target_route = route_set.add_route('/test/part/{!test,one/more/time}')
98
+ route_set.recognize(build_request({:method => 'get', :path => '/test/part/one/more/time'})).path.route.should == target_route
99
+ route_set.recognize(build_request({:method => 'get', :path => '/test/part/one/more/time'})).params.should == [[:test, 'one/more/time']]
100
+ end
101
+
102
+ it "should recgonize a greedy regex single variable with static parts after" do
103
+ target_route = route_set.add_route('/test/part/{!test,one/more/time}/help')
104
+ route_set.recognize(build_request({:method => 'get', :path => '/test/part/one/more/time/help'})).path.route.should == target_route
105
+ route_set.recognize(build_request({:method => 'get', :path => '/test/part/one/more/time/help'})).params.should == [[:test, 'one/more/time']]
106
+ end
107
+
96
108
  it "should recgonize two glob-style variables separated by a static part" do
97
109
  target_route = route_set.add_route('/*format/innovate/*onemore')
98
110
  response = route_set.recognize(build_request({:method => 'get', :path => '/sample/html/innovate/apple'}))
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.8
4
+ version: 0.4.10
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-06-16 00:00:00 -07:00
12
+ date: 2009-07-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.3
23
+ version: 0.0.5
24
24
  version:
25
25
  description: A general purpose routing library
26
26
  email: joshbuddy@gmail.com
@@ -38,7 +38,6 @@ files:
38
38
  - VERSION.yml
39
39
  - lib/usher.rb
40
40
  - lib/usher/exceptions.rb
41
- - lib/usher/generate.rb
42
41
  - lib/usher/grapher.rb
43
42
  - lib/usher/interface.rb
44
43
  - lib/usher/interface/email_interface.rb
@@ -55,10 +54,14 @@ files:
55
54
  - lib/usher/route/request_method.rb
56
55
  - lib/usher/route/variable.rb
57
56
  - lib/usher/splitter.rb
57
+ - lib/usher/util.rb
58
+ - lib/usher/util/generate.rb
59
+ - lib/usher/util/parser.rb
58
60
  - rails/init.rb
59
61
  - spec/private/email/recognize_spec.rb
60
62
  - spec/private/generate_spec.rb
61
63
  - spec/private/grapher_spec.rb
64
+ - spec/private/parser_spec.rb
62
65
  - spec/private/path_spec.rb
63
66
  - spec/private/rack/dispatch_spec.rb
64
67
  - spec/private/rails2_2/compat.rb
@@ -71,7 +74,6 @@ files:
71
74
  - spec/private/rails2_3/recognize_spec.rb
72
75
  - spec/private/recognize_spec.rb
73
76
  - spec/private/request_method_spec.rb
74
- - spec/private/split_spec.rb
75
77
  - spec/spec.opts
76
78
  has_rdoc: false
77
79
  homepage: http://github.com/joshbuddy/usher
@@ -103,6 +105,7 @@ test_files:
103
105
  - spec/private/email/recognize_spec.rb
104
106
  - spec/private/generate_spec.rb
105
107
  - spec/private/grapher_spec.rb
108
+ - spec/private/parser_spec.rb
106
109
  - spec/private/path_spec.rb
107
110
  - spec/private/rack/dispatch_spec.rb
108
111
  - spec/private/rails2_2/compat.rb
@@ -115,4 +118,3 @@ test_files:
115
118
  - spec/private/rails2_3/recognize_spec.rb
116
119
  - spec/private/recognize_spec.rb
117
120
  - spec/private/request_method_spec.rb
118
- - spec/private/split_spec.rb
@@ -1,131 +0,0 @@
1
- require 'rack'
2
-
3
- unless Rack::Utils.respond_to?(:uri_escape)
4
- module Rack
5
-
6
- module Utils
7
-
8
- def uri_escape(s)
9
- s.to_s.gsub(/([^:\/?\[\]\-_~\.!\$&'\(\)\*\+,;=@a-zA-Z0-9]+)/n) {
10
- '%'<<$1.unpack('H2'*$1.size).join('%').upcase
11
- }.tr(' ', '+')
12
- end
13
- module_function :uri_escape
14
-
15
- def uri_unescape(s)
16
- gsub(/((?:%[0-9a-fA-F]{2})+)/n){
17
- [$1.delete('%')].pack('H*')
18
- }
19
- end
20
- module_function :uri_unescape
21
-
22
- end
23
- end
24
- end
25
-
26
- class Usher
27
- class Generators
28
-
29
- class URL
30
-
31
- def initialize(usher)
32
- @usher = usher
33
- end
34
-
35
- def generate_full(routing_lookup, request, params = nil)
36
- path = path_for_routing_lookup(routing_lookup, params)
37
- result = generate_start(path, request)
38
- result << generate_path(path, params)
39
- end
40
-
41
- def generate(routing_lookup, params = nil)
42
- generate_path(path_for_routing_lookup(routing_lookup, params), params)
43
- end
44
-
45
- def generate_start(path, request)
46
- result = (path.route.generate_with && path.route.generate_with.scheme || request.scheme).dup
47
- result << '://'
48
- result << (path.route.generate_with && path.route.generate_with.host) ? path.route.generate_with.host : request.host
49
- port = path.route.generate_with && path.route.generate_with.port || request.port
50
- if result[4] == ?s
51
- result << ':' << port.to_s if port != 443
52
- else
53
- result << ':' << port.to_s if port != 80
54
- end
55
- result
56
- end
57
-
58
- def path_for_routing_lookup(routing_lookup, params)
59
- path = case routing_lookup
60
- when Symbol
61
- route = @usher.named_routes[routing_lookup]
62
- params.is_a?(Hash) ? route.find_matching_path(params) : route.paths.first
63
- when Route
64
- params.is_a?(Hash) ? routing_lookup.find_matching_path(params) : routing_lookup.paths.first
65
- when nil
66
- params.is_a?(Hash) ? @usher.path_for_options(params) : raise
67
- when Route::Path
68
- routing_lookup
69
- end
70
- end
71
-
72
- # Generates a completed URL based on a +route+ or set of optional +params+
73
- #
74
- # set = Usher.new
75
- # route = set.add_named_route(:test_route, '/:controller/:action')
76
- # set.generate_url(nil, {:controller => 'c', :action => 'a'}) == '/c/a' => true
77
- # set.generate_url(:test_route, {:controller => 'c', :action => 'a'}) == '/c/a' => true
78
- # set.generate_url(route.primary_path, {:controller => 'c', :action => 'a'}) == '/c/a' => true
79
- def generate_path(path, params = nil)
80
- raise UnrecognizedException.new unless path
81
-
82
- params = Array(params) if params.is_a?(String)
83
- if params.is_a?(Array)
84
- given_size = params.size
85
- extra_params = params.last.is_a?(Hash) ? params.pop : nil
86
- params = Hash[*path.dynamic_parts.inject([]){|a, dynamic_part| a.concat([dynamic_part.name, params.shift || raise(MissingParameterException.new("got #{given_size}, expected #{path.dynamic_parts.size} parameters"))]); a}]
87
- params.merge!(extra_params) if extra_params
88
- end
89
-
90
- result = ''
91
- path.parts.each do |part|
92
- case part
93
- when Route::Variable
94
- value = (params && params.delete(part.name)) || part.default_value || raise(MissingParameterException.new)
95
- case part.type
96
- when :*
97
- value.each_with_index do |current_value, index|
98
- current_value = current_value.to_s unless current_value.is_a?(String)
99
- part.valid!(current_value)
100
- result << current_value
101
- result << '/' if index != value.size - 1
102
- end
103
- when :':'
104
- value = value.to_s unless value.is_a?(String)
105
- part.valid!(value)
106
- result << value
107
- end
108
- else
109
- result << part
110
- end
111
- end
112
- result = Rack::Utils.uri_escape(result)
113
-
114
- if params && !params.empty?
115
- has_query = result[??]
116
- params.each do |k,v|
117
- case v
118
- when Array
119
- v.each do |v_part|
120
- result << (has_query ? '&' : has_query = true && '?') << Rack::Utils.escape("#{k.to_s}[]") << '=' << Rack::Utils.escape(v_part.to_s)
121
- end
122
- else
123
- result << (has_query ? '&' : has_query = true && '?') << Rack::Utils.escape(k.to_s) << '=' << Rack::Utils.escape(v.to_s)
124
- end
125
- end
126
- end
127
- result
128
- end
129
- end
130
- end
131
- end
@@ -1,76 +0,0 @@
1
- require 'lib/usher'
2
-
3
- describe "Usher route tokenizing" do
4
-
5
-
6
- it "should split / delimited routes" do
7
- Usher::Splitter.for_delimiters(['/', '.'], '[0-9A-Za-z\$\-_\+!\*\',]+').split('/test/this/split').should == [['/', 'test', '/','this', '/', 'split']]
8
- end
9
-
10
- it "should split / delimited routes with a regex in it" do
11
- Usher::Splitter.for_delimiters(['/', '.'], '[0-9A-Za-z\$\-_\+!\*\',]+').
12
- split('/test/{this}/split').should == [['/', 'test', '/', /this/, '/', 'split']]
13
- end
14
-
15
- it "should split on ' ' delimited routes as well" do
16
- Usher::Splitter.for_delimiters([' '], '[0-9A-Za-z\$\-_\+!\*\',]+').split('test this split').should == [['test', ' ', 'this', ' ', 'split']]
17
- end
18
-
19
- it "should split on email delimiters as well" do
20
- Usher::Splitter.for_delimiters(['@', '+', '-', '.'], '[a-zA-Z0-9]+').split('one+more.12345-09876-alphanum3ric5@domain.com').should == [["one", '+', "more", ".", "12345", '-', "09876", '-', "alphanum3ric5", "@", "domain", ".", "com"]]
21
- end
22
-
23
- it "should split on ' ' delimited routes for more complex routes as well" do
24
- Usher::Splitter.for_delimiters([' '], '[0-9A-Za-z\$\-_\+!\*\',]+').split('(test|this) split').should == [['test', ' ', 'split'], ['this', ' ', 'split']]
25
- end
26
-
27
- it "should group optional parts with brackets" do
28
- Usher::Splitter.for_delimiters(['/', '.'], '[0-9A-Za-z\$\-_\+!\*\',]+').split('/test/this(/split)').should == [
29
- ['/', 'test', '/', 'this'],
30
- ['/', 'test', '/', 'this', '/', 'split']
31
- ]
32
- end
33
-
34
- it "should group exclusive optional parts with brackets and pipes" do
35
- Usher::Splitter.for_delimiters(['/', '.'], '[0-9A-Za-z\$\-_\+!\*\',]+').split('/test/this(/split|/split2)').should == [
36
- ['/', 'test', '/', 'this','/', 'split'],
37
- ['/', 'test', '/', 'this','/', 'split2']
38
- ]
39
- end
40
-
41
- it "should group exclusive optional-optional parts with brackets and pipes" do
42
- Usher::Splitter.for_delimiters(['/', '.'], '[0-9A-Za-z\$\-_\+!\*\',]+').split('/test/this((/split|/split2))').should == [
43
- ['/', 'test','/', 'this'],
44
- ['/', 'test','/', 'this', '/', 'split'],
45
- ['/', 'test','/', 'this', '/', 'split2']
46
- ]
47
- end
48
-
49
- it "should group optional parts with brackets (for non overlapping groups)" do
50
- Usher::Splitter.for_delimiters(['/', '.'], '[0-9A-Za-z\$\-_\+!\*\',]+').split('/test/this(/split)(/split2)') == [
51
- ['/', "test", '/', "this"],
52
- ['/', "test", '/', "this", '/', "split"],
53
- ['/', "test", '/', "this", '/', "split2"],
54
- ['/', "test", '/', "this", '/', "split", '/', "split2"]
55
- ]
56
- end
57
-
58
- it "should group nested-optional parts with brackets" do
59
- Usher::Splitter.for_delimiters(['/', '.'], '[0-9A-Za-z\$\-_\+!\*\',]+').split('/test/this(/split(.:format))') == [
60
- ['/', "test", '/', "this"],
61
- ['/', "test", '/', "this", '/', "split"],
62
- ['/', "test", '/', "this", '/', "split", '.', Usher::Route::Variable.new(:':', :format)]
63
- ]
64
- end
65
-
66
- it "should to_s all different variable types" do
67
- Usher::Splitter.for_delimiters(['/', '.'], '[0-9A-Za-z\$\-_\+!\*\',]+').split('/:split/*splitter').first.collect{|v| v.to_s} ==
68
- [ ':split', '*splitter' ]
69
- end
70
-
71
- it "should == variable types" do
72
- parts = Usher::Splitter.for_delimiters(['/', '.'], '[0-9A-Za-z\$\-_\+!\*\',]+').split('/:split/:split').first
73
- parts[1].should == parts[3]
74
- end
75
-
76
- end