rack-mount 0.0.1 → 0.6.13

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.
Files changed (45) hide show
  1. data/README.rdoc +12 -4
  2. data/lib/rack/mount/analysis/frequency.rb +15 -6
  3. data/lib/rack/mount/analysis/histogram.rb +55 -6
  4. data/lib/rack/mount/analysis/splitting.rb +85 -71
  5. data/lib/rack/mount/code_generation.rb +117 -0
  6. data/lib/rack/mount/generatable_regexp.rb +95 -48
  7. data/lib/rack/mount/multimap.rb +2 -43
  8. data/lib/rack/mount/prefix.rb +12 -7
  9. data/lib/rack/mount/regexp_with_named_groups.rb +27 -7
  10. data/lib/rack/mount/route.rb +79 -18
  11. data/lib/rack/mount/route_set.rb +330 -22
  12. data/lib/rack/mount/strexp/parser.rb +0 -0
  13. data/lib/rack/mount/strexp/parser.y +34 -0
  14. data/lib/rack/mount/strexp/tokenizer.rb +83 -0
  15. data/lib/rack/mount/strexp/tokenizer.rex +12 -0
  16. data/lib/rack/mount/strexp.rb +54 -79
  17. data/lib/rack/mount/utils.rb +65 -174
  18. data/lib/rack/mount/vendor/multimap/multimap.rb +142 -39
  19. data/lib/rack/mount/vendor/multimap/multiset.rb +33 -1
  20. data/lib/rack/mount/vendor/multimap/nested_multimap.rb +18 -16
  21. data/lib/rack/mount/vendor/regin/regin/alternation.rb +40 -0
  22. data/lib/rack/mount/vendor/regin/regin/anchor.rb +4 -0
  23. data/lib/rack/mount/vendor/regin/regin/atom.rb +54 -0
  24. data/lib/rack/mount/vendor/regin/regin/character.rb +51 -0
  25. data/lib/rack/mount/vendor/regin/regin/character_class.rb +50 -0
  26. data/lib/rack/mount/vendor/regin/regin/collection.rb +77 -0
  27. data/lib/rack/mount/vendor/regin/regin/expression.rb +126 -0
  28. data/lib/rack/mount/vendor/regin/regin/group.rb +85 -0
  29. data/lib/rack/mount/vendor/regin/regin/options.rb +55 -0
  30. data/lib/rack/mount/vendor/regin/regin/parser.rb +521 -0
  31. data/lib/rack/mount/vendor/regin/regin/tokenizer.rb +0 -0
  32. data/lib/rack/mount/vendor/regin/regin/version.rb +3 -0
  33. data/lib/rack/mount/vendor/regin/regin.rb +75 -0
  34. data/lib/rack/mount/version.rb +3 -0
  35. data/lib/rack/mount.rb +13 -16
  36. metadata +73 -29
  37. data/lib/rack/mount/const.rb +0 -45
  38. data/lib/rack/mount/exceptions.rb +0 -3
  39. data/lib/rack/mount/generation/route.rb +0 -57
  40. data/lib/rack/mount/generation/route_set.rb +0 -163
  41. data/lib/rack/mount/meta_method.rb +0 -104
  42. data/lib/rack/mount/mixover.rb +0 -47
  43. data/lib/rack/mount/recognition/code_generation.rb +0 -99
  44. data/lib/rack/mount/recognition/route.rb +0 -59
  45. data/lib/rack/mount/recognition/route_set.rb +0 -88
@@ -3,16 +3,12 @@ require 'rack/mount/route'
3
3
  require 'rack/mount/utils'
4
4
 
5
5
  module Rack::Mount
6
- class RouteSet
7
- extend Mixover
8
-
9
- # Include generation and recognition concerns
10
- include Generation::RouteSet, Recognition::RouteSet
11
- include Recognition::CodeGeneration
6
+ class RoutingError < StandardError; end
12
7
 
8
+ class RouteSet
13
9
  # Initialize a new RouteSet without optimizations
14
- def self.new_without_optimizations(*args, &block)
15
- new_without_module(Recognition::CodeGeneration, *args, &block)
10
+ def self.new_without_optimizations(options = {}, &block)
11
+ new(options.merge(:_optimize => false), &block)
16
12
  end
17
13
 
18
14
  # Basic RouteSet initializer.
@@ -23,7 +19,20 @@ module Rack::Mount
23
19
  # - <tt>Generation::RouteSet.new</tt>
24
20
  # - <tt>Recognition::RouteSet.new</tt>
25
21
  def initialize(options = {}, &block)
22
+ @parameters_key = options.delete(:parameters_key) || 'rack.routing_args'
23
+ @parameters_key.freeze
24
+
25
+ @named_routes = {}
26
+
27
+ @recognition_key_analyzer = Analysis::Splitting.new
28
+ @generation_key_analyzer = Analysis::Frequency.new
29
+
26
30
  @request_class = options.delete(:request_class) || Rack::Request
31
+ @valid_conditions = @request_class.public_instance_methods.map! { |m| m.to_sym }
32
+
33
+ extend CodeGeneration unless options[:_optimize] == false
34
+ @optimized_recognize_defined = false
35
+
27
36
  @routes = []
28
37
  expire!
29
38
 
@@ -40,28 +49,194 @@ module Rack::Mount
40
49
  # Conditions may be expressed as strings or
41
50
  # regexps to match against.
42
51
  # <tt>defaults</tt>:: A hash of values that always gets merged in
43
- # <tt>name</tt>:: Symbol identifier for the route used with named
52
+ # <tt>name</tt>:: Symbol identifier for the route used with named
44
53
  # route generations
45
54
  def add_route(app, conditions = {}, defaults = {}, name = nil)
46
- route = Route.new(self, app, conditions, defaults, name)
55
+ unless conditions.is_a?(Hash)
56
+ raise ArgumentError, 'conditions must be a Hash'
57
+ end
58
+
59
+ unless conditions.all? { |method, pattern|
60
+ @valid_conditions.include?(method)
61
+ }
62
+ raise ArgumentError, 'conditions may only include ' +
63
+ @valid_conditions.inspect
64
+ end
65
+
66
+ route = Route.new(app, conditions, defaults, name)
47
67
  @routes << route
68
+
69
+ @recognition_key_analyzer << route.conditions
70
+
71
+ @named_routes[route.name] = route if route.name
72
+ @generation_key_analyzer << route.generation_keys
73
+
48
74
  expire!
49
75
  route
50
76
  end
51
77
 
52
- # See <tt>Recognition::RouteSet#call</tt>
78
+ def recognize(obj)
79
+ raise 'route set not finalized' unless @recognition_graph
80
+
81
+ cache = {}
82
+ keys = @recognition_keys.map { |key|
83
+ if key.respond_to?(:call)
84
+ key.call(cache, obj)
85
+ else
86
+ obj.send(key)
87
+ end
88
+ }
89
+
90
+ @recognition_graph[*keys].each do |route|
91
+ matches = {}
92
+ params = route.defaults.dup
93
+
94
+ if route.conditions.all? { |method, condition|
95
+ value = obj.send(method)
96
+ if condition.is_a?(Regexp) && (m = value.match(condition))
97
+ matches[method] = m
98
+ captures = m.captures
99
+ route.named_captures[method].each do |k, i|
100
+ if v = captures[i]
101
+ params[k] = v
102
+ end
103
+ end
104
+ true
105
+ elsif value == condition
106
+ true
107
+ else
108
+ false
109
+ end
110
+ }
111
+ if block_given?
112
+ yield route, matches, params
113
+ else
114
+ return route, matches, params
115
+ end
116
+ end
117
+ end
118
+
119
+ nil
120
+ end
121
+
122
+ X_CASCADE = 'X-Cascade'.freeze
123
+ PASS = 'pass'.freeze
124
+ PATH_INFO = 'PATH_INFO'.freeze
125
+
126
+ # Rack compatible recognition and dispatching method. Routes are
127
+ # tried until one returns a non-catch status code. If no routes
128
+ # match, the catch status code is returned.
129
+ #
130
+ # This method can only be invoked after the RouteSet has been
131
+ # finalized.
53
132
  def call(env)
54
- raise NotImplementedError
133
+ raise 'route set not finalized' unless @recognition_graph
134
+
135
+ env[PATH_INFO] = Utils.normalize_path(env[PATH_INFO])
136
+
137
+ request = nil
138
+ req = @request_class.new(env)
139
+ recognize(req) do |route, matches, params|
140
+ # TODO: We only want to unescape params from uri related methods
141
+ params.each { |k, v| params[k] = Utils.unescape_uri(v) if v.is_a?(String) }
142
+
143
+ if route.prefix?
144
+ env[Prefix::KEY] = matches[:path_info].to_s
145
+ end
146
+
147
+ env[@parameters_key] = params
148
+ result = route.app.call(env)
149
+ return result unless result[1][X_CASCADE] == PASS
150
+ end
151
+
152
+ request || [404, {'Content-Type' => 'text/html', 'X-Cascade' => 'pass'}, ['Not Found']]
55
153
  end
56
154
 
57
- # See <tt>Generation::RouteSet#url</tt>
58
- def url(*args)
59
- raise NotImplementedError
155
+ # Generates a url from Rack env and identifiers or significant keys.
156
+ #
157
+ # To generate a url by named route, pass the name in as a +Symbol+.
158
+ # url(env, :dashboard) # => "/dashboard"
159
+ #
160
+ # Additional parameters can be passed in as a hash
161
+ # url(env, :people, :id => "1") # => "/people/1"
162
+ #
163
+ # If no name route is given, it will fall back to a slower
164
+ # generation search.
165
+ # url(env, :controller => "people", :action => "show", :id => "1")
166
+ # # => "/people/1"
167
+ def url(env, *args)
168
+ named_route, params = nil, {}
169
+
170
+ case args.length
171
+ when 2
172
+ named_route, params = args[0], args[1].dup
173
+ when 1
174
+ if args[0].is_a?(Hash)
175
+ params = args[0].dup
176
+ else
177
+ named_route = args[0]
178
+ end
179
+ else
180
+ raise ArgumentError
181
+ end
182
+
183
+ only_path = params.delete(:only_path)
184
+ recall = env[@parameters_key] || {}
185
+
186
+ unless result = generate(:all, named_route, params, recall,
187
+ :parameterize => lambda { |name, param| Utils.escape_uri(param) })
188
+ return
189
+ end
190
+
191
+ parts, params = result
192
+ return unless parts
193
+
194
+ params.each do |k, v|
195
+ if v
196
+ params[k] = v
197
+ else
198
+ params.delete(k)
199
+ end
200
+ end
201
+
202
+ req = stubbed_request_class.new(env)
203
+ req._stubbed_values = parts.merge(:query_string => Utils.build_nested_query(params))
204
+ only_path ? req.fullpath : req.url
60
205
  end
61
206
 
62
- # See <tt>Generation::RouteSet#generate</tt>
63
- def generate(*args)
64
- raise NotImplementedError
207
+ def generate(method, *args) #:nodoc:
208
+ raise 'route set not finalized' unless @generation_graph
209
+
210
+ method = nil if method == :all
211
+ named_route, params, recall, options = extract_params!(*args)
212
+ merged = recall.merge(params)
213
+ route = nil
214
+
215
+ if named_route
216
+ if route = @named_routes[named_route.to_sym]
217
+ recall = route.defaults.merge(recall)
218
+ url = route.generate(method, params, recall, options)
219
+ [url, params]
220
+ else
221
+ raise RoutingError, "#{named_route} failed to generate from #{params.inspect}"
222
+ end
223
+ else
224
+ keys = @generation_keys.map { |key|
225
+ if k = merged[key]
226
+ k.to_s
227
+ else
228
+ nil
229
+ end
230
+ }
231
+ @generation_graph[*keys].each do |r|
232
+ next unless r.significant_params?
233
+ if url = r.generate(method, params, recall, options)
234
+ return [url, params]
235
+ end
236
+ end
237
+
238
+ raise RoutingError, "No route matches #{params.inspect}"
239
+ end
65
240
  end
66
241
 
67
242
  # Number of routes in the set
@@ -70,6 +245,14 @@ module Rack::Mount
70
245
  end
71
246
 
72
247
  def rehash #:nodoc:
248
+ Utils.debug "rehashing"
249
+
250
+ @recognition_keys = build_recognition_keys
251
+ @recognition_graph = build_recognition_graph
252
+ @generation_keys = build_generation_keys
253
+ @generation_graph = build_generation_graph
254
+
255
+ self
73
256
  end
74
257
 
75
258
  # Finalizes the set and builds optimized data structures. You *must*
@@ -78,6 +261,11 @@ module Rack::Mount
78
261
  def freeze
79
262
  unless frozen?
80
263
  rehash
264
+
265
+ @recognition_key_analyzer = nil
266
+ @generation_key_analyzer = nil
267
+ @valid_conditions = nil
268
+
81
269
  @routes.each { |route| route.freeze }
82
270
  @routes.freeze
83
271
  end
@@ -85,8 +273,46 @@ module Rack::Mount
85
273
  super
86
274
  end
87
275
 
276
+ def marshal_dump #:nodoc:
277
+ hash = {}
278
+
279
+ instance_variables_to_serialize.each do |ivar|
280
+ hash[ivar] = instance_variable_get(ivar)
281
+ end
282
+
283
+ if graph = hash[:@recognition_graph]
284
+ hash[:@recognition_graph] = graph.dup
285
+ end
286
+
287
+ hash
288
+ end
289
+
290
+ def marshal_load(hash) #:nodoc:
291
+ hash.each do |ivar, value|
292
+ instance_variable_set(ivar, value)
293
+ end
294
+ end
295
+
296
+ protected
297
+ def recognition_stats
298
+ { :keys => @recognition_keys,
299
+ :keys_size => @recognition_keys.size,
300
+ :graph_size => @recognition_graph.size,
301
+ :graph_height => @recognition_graph.height,
302
+ :graph_average_height => @recognition_graph.average_height }
303
+ end
304
+
88
305
  private
89
306
  def expire! #:nodoc:
307
+ @recognition_keys = @recognition_graph = nil
308
+ @recognition_key_analyzer.expire!
309
+
310
+ @generation_keys = @generation_graph = nil
311
+ @generation_key_analyzer.expire!
312
+ end
313
+
314
+ def instance_variables_to_serialize
315
+ instance_variables.map { |ivar| ivar.to_sym } - [:@stubbed_request_class, :@optimized_recognize_defined]
90
316
  end
91
317
 
92
318
  # An internal helper method for constructing a nested set from
@@ -98,12 +324,94 @@ module Rack::Mount
98
324
  def build_nested_route_set(keys, &block)
99
325
  graph = Multimap.new
100
326
  @routes.each_with_index do |route, index|
101
- k = keys.map { |key| block.call(key, index) }
102
- Utils.pop_trailing_nils!(k)
103
- k.map! { |key| key || /.+/ }
104
- graph[*k] = route
327
+ catch(:skip) do
328
+ k = keys.map { |key| block.call(key, index) }
329
+ Utils.pop_trailing_blanks!(k)
330
+ k.map! { |key| key || /.*/ }
331
+ graph[*k] = route
332
+ end
105
333
  end
106
334
  graph
107
335
  end
336
+
337
+ def build_recognition_graph
338
+ build_nested_route_set(@recognition_keys) { |k, i|
339
+ @recognition_key_analyzer.possible_keys[i][k]
340
+ }
341
+ end
342
+
343
+ def build_recognition_keys
344
+ keys = @recognition_key_analyzer.report
345
+ Utils.debug "recognition keys - #{keys.inspect}"
346
+ keys
347
+ end
348
+
349
+ def build_generation_graph
350
+ build_nested_route_set(@generation_keys) { |k, i|
351
+ throw :skip unless @routes[i].significant_params?
352
+
353
+ if k = @generation_key_analyzer.possible_keys[i][k]
354
+ k.to_s
355
+ else
356
+ nil
357
+ end
358
+ }
359
+ end
360
+
361
+ def build_generation_keys
362
+ keys = @generation_key_analyzer.report
363
+ Utils.debug "generation keys - #{keys.inspect}"
364
+ keys
365
+ end
366
+
367
+ def extract_params!(*args)
368
+ case args.length
369
+ when 4
370
+ named_route, params, recall, options = args
371
+ when 3
372
+ if args[0].is_a?(Hash)
373
+ params, recall, options = args
374
+ else
375
+ named_route, params, recall = args
376
+ end
377
+ when 2
378
+ if args[0].is_a?(Hash)
379
+ params, recall = args
380
+ else
381
+ named_route, params = args
382
+ end
383
+ when 1
384
+ if args[0].is_a?(Hash)
385
+ params = args[0]
386
+ else
387
+ named_route = args[0]
388
+ end
389
+ else
390
+ raise ArgumentError
391
+ end
392
+
393
+ named_route ||= nil
394
+ params ||= {}
395
+ recall ||= {}
396
+ options ||= {}
397
+
398
+ [named_route, params.dup, recall.dup, options.dup]
399
+ end
400
+
401
+ def stubbed_request_class
402
+ @stubbed_request_class ||= begin
403
+ klass = Class.new(@request_class)
404
+ klass.public_instance_methods.each do |method|
405
+ next if method =~ /^__|object_id/
406
+ klass.class_eval <<-RUBY
407
+ def #{method}(*args, &block)
408
+ @_stubbed_values[:#{method}] || super
409
+ end
410
+ RUBY
411
+ end
412
+ klass.class_eval { attr_accessor :_stubbed_values }
413
+ klass
414
+ end
415
+ end
108
416
  end
109
417
  end
Binary file
@@ -0,0 +1,34 @@
1
+ class Rack::Mount::StrexpParser
2
+ rule
3
+ target: expr { result = anchor ? "\\A#{val.join}\\Z" : "\\A#{val.join}" }
4
+
5
+ expr: expr token { result = val.join }
6
+ | token
7
+
8
+ token: PARAM {
9
+ name = val[0].to_sym
10
+ requirement = requirements[name]
11
+ result = REGEXP_NAMED_CAPTURE % [name, requirement]
12
+ }
13
+ | GLOB {
14
+ name = val[0].to_sym
15
+ requirement = requirements[name]
16
+ result = REGEXP_NAMED_CAPTURE % [name, '.+' || requirement]
17
+ }
18
+ | LPAREN expr RPAREN { result = "(?:#{val[1]})?" }
19
+ | CHAR { result = Regexp.escape(val[0]) }
20
+ end
21
+
22
+ ---- header ----
23
+ require 'rack/mount/utils'
24
+ require 'rack/mount/strexp/tokenizer'
25
+
26
+ ---- inner
27
+
28
+ if Regin.regexp_supports_named_captures?
29
+ REGEXP_NAMED_CAPTURE = '(?<%s>%s)'.freeze
30
+ else
31
+ REGEXP_NAMED_CAPTURE = '(?:<%s>%s)'.freeze
32
+ end
33
+
34
+ attr_accessor :anchor, :requirements
@@ -0,0 +1,83 @@
1
+ #--
2
+ # DO NOT MODIFY!!!!
3
+ # This file is automatically generated by rex 1.0.5.beta1
4
+ # from lexical definition file "lib/rack/mount/strexp/tokenizer.rex".
5
+ #++
6
+
7
+ require 'racc/parser'
8
+ class Rack::Mount::StrexpParser < Racc::Parser
9
+ require 'strscan'
10
+
11
+ class ScanError < StandardError ; end
12
+
13
+ attr_reader :lineno
14
+ attr_reader :filename
15
+ attr_accessor :state
16
+
17
+ def scan_setup(str)
18
+ @ss = StringScanner.new(str)
19
+ @lineno = 1
20
+ @state = nil
21
+ end
22
+
23
+ def action
24
+ yield
25
+ end
26
+
27
+ def scan_str(str)
28
+ scan_setup(str)
29
+ do_parse
30
+ end
31
+ alias :scan :scan_str
32
+
33
+ def load_file( filename )
34
+ @filename = filename
35
+ open(filename, "r") do |f|
36
+ scan_setup(f.read)
37
+ end
38
+ end
39
+
40
+ def scan_file( filename )
41
+ load_file(filename)
42
+ do_parse
43
+ end
44
+
45
+
46
+ def next_token
47
+ return if @ss.eos?
48
+
49
+ text = @ss.peek(1)
50
+ @lineno += 1 if text == "\n"
51
+ token = case @state
52
+ when nil
53
+ case
54
+ when (text = @ss.scan(/\\(\(|\)|:|\*)/))
55
+ action { [:CHAR, @ss[1]] }
56
+
57
+ when (text = @ss.scan(/\:([a-zA-Z_]\w*)/))
58
+ action { [:PARAM, @ss[1]] }
59
+
60
+ when (text = @ss.scan(/\*([a-zA-Z_]\w*)/))
61
+ action { [:GLOB, @ss[1]] }
62
+
63
+ when (text = @ss.scan(/\(/))
64
+ action { [:LPAREN, text] }
65
+
66
+ when (text = @ss.scan(/\)/))
67
+ action { [:RPAREN, text] }
68
+
69
+ when (text = @ss.scan(/./))
70
+ action { [:CHAR, text] }
71
+
72
+ else
73
+ text = @ss.string[@ss.pos .. -1]
74
+ raise ScanError, "can not match: '" + text + "'"
75
+ end # if
76
+
77
+ else
78
+ raise ScanError, "undefined state: '" + state.to_s + "'"
79
+ end # case state
80
+ token
81
+ end # def next_token
82
+
83
+ end # class
@@ -0,0 +1,12 @@
1
+ class Rack::Mount::StrexpParser
2
+ macro
3
+ RESERVED \(|\)|:|\*
4
+ ALPHA_U [a-zA-Z_]
5
+ rule
6
+ \\({RESERVED}) { [:CHAR, @ss[1]] }
7
+ \:({ALPHA_U}\w*) { [:PARAM, @ss[1]] }
8
+ \*({ALPHA_U}\w*) { [:GLOB, @ss[1]] }
9
+ \( { [:LPAREN, text] }
10
+ \) { [:RPAREN, text] }
11
+ . { [:CHAR, text] }
12
+ end
@@ -1,93 +1,68 @@
1
- require 'strscan'
1
+ require 'rack/mount/strexp/parser'
2
2
 
3
3
  module Rack::Mount
4
- class Strexp < Regexp
5
- # Parses segmented string expression and converts it into a Regexp
6
- #
7
- # Strexp.compile('foo')
8
- # # => %r{\Afoo\Z}
9
- #
10
- # Strexp.compile('foo/:bar', {}, ['/'])
11
- # # => %r{\Afoo/(?<bar>[^/]+)\Z}
12
- #
13
- # Strexp.compile(':foo.example.com')
14
- # # => %r{\A(?<foo>.+)\.example\.com\Z}
15
- #
16
- # Strexp.compile('foo/:bar', {:bar => /[a-z]+/}, ['/'])
17
- # # => %r{\Afoo/(?<bar>[a-z]+)\Z}
18
- #
19
- # Strexp.compile('foo(.:extension)')
20
- # # => %r{\Afoo(\.(?<extension>.+))?\Z}
21
- #
22
- # Strexp.compile('src/*files')
23
- # # => %r{\Asrc/(?<files>.+)\Z}
24
- def initialize(str, requirements = {}, separators = [])
25
- return super(str) if str.is_a?(Regexp)
4
+ class Strexp
5
+ class << self
6
+ # Parses segmented string expression and converts it into a Regexp
7
+ #
8
+ # Strexp.compile('foo')
9
+ # # => %r{\Afoo\Z}
10
+ #
11
+ # Strexp.compile('foo/:bar', {}, ['/'])
12
+ # # => %r{\Afoo/(?<bar>[^/]+)\Z}
13
+ #
14
+ # Strexp.compile(':foo.example.com')
15
+ # # => %r{\A(?<foo>.+)\.example\.com\Z}
16
+ #
17
+ # Strexp.compile('foo/:bar', {:bar => /[a-z]+/}, ['/'])
18
+ # # => %r{\Afoo/(?<bar>[a-z]+)\Z}
19
+ #
20
+ # Strexp.compile('foo(.:extension)')
21
+ # # => %r{\Afoo(\.(?<extension>.+))?\Z}
22
+ #
23
+ # Strexp.compile('src/*files')
24
+ # # => %r{\Asrc/(?<files>.+)\Z}
25
+ def compile(str, requirements = {}, separators = [], anchor = true)
26
+ return Regexp.compile(str) if str.is_a?(Regexp)
26
27
 
27
- re = Regexp.escape(str)
28
- requirements = requirements ? requirements.dup : {}
28
+ requirements = requirements ? requirements.dup : {}
29
+ normalize_requirements!(requirements, separators)
29
30
 
30
- normalize_requirements!(requirements, separators)
31
- parse_dynamic_segments!(re, requirements)
32
- parse_optional_segments!(re)
31
+ parser = StrexpParser.new
32
+ parser.anchor = anchor
33
+ parser.requirements = requirements
33
34
 
34
- super("\\A#{re}\\Z")
35
- end
36
-
37
- private
38
- def normalize_requirements!(requirements, separators)
39
- requirements.each do |key, value|
40
- if value.is_a?(Regexp)
41
- if regexp_has_modifiers?(value)
42
- requirements[key] = value
43
- else
44
- requirements[key] = value.source
45
- end
46
- else
47
- requirements[key] = Regexp.escape(value)
48
- end
35
+ begin
36
+ re = parser.scan_str(str)
37
+ rescue Racc::ParseError => e
38
+ raise RegexpError, e.message
49
39
  end
50
- requirements.default ||= separators.any? ?
51
- "[^#{separators.join}]+" : '.+'
52
- requirements
53
- end
54
40
 
55
- def parse_dynamic_segments!(str, requirements)
56
- re, pos, scanner = '', 0, StringScanner.new(str)
57
- while scanner.scan_until(/(:|\\\*)([a-zA-Z_]\w*)/)
58
- pre, pos = scanner.pre_match[pos..-1], scanner.pos
59
- if pre =~ /(.*)\\\\\Z/
60
- re << $1 + scanner.matched
61
- else
62
- name = scanner[2].to_sym
63
- requirement = scanner[1] == ':' ?
64
- requirements[name] : '.+'
65
- re << pre + Const::REGEXP_NAMED_CAPTURE % [name, requirement]
66
- end
67
- end
68
- re << scanner.rest
69
- str.replace(re)
41
+ Regexp.compile(re)
70
42
  end
43
+ alias_method :new, :compile
71
44
 
72
- def parse_optional_segments!(str)
73
- re, pos, scanner = '', 0, StringScanner.new(str)
74
- while scanner.scan_until(/\\\(|\\\)/)
75
- pre, pos = scanner.pre_match[pos..-1], scanner.pos
76
- if pre =~ /(.*)\\\\\Z/
77
- re << $1 + scanner.matched
78
- elsif scanner.matched == '\\('
79
- # re << pre + '(?:'
80
- re << pre + '('
81
- elsif scanner.matched == '\\)'
82
- re << pre + ')?'
45
+ private
46
+ def normalize_requirements!(requirements, separators)
47
+ requirements.each do |key, value|
48
+ if value.is_a?(Regexp)
49
+ if regexp_has_modifiers?(value)
50
+ requirements[key] = value
51
+ else
52
+ requirements[key] = value.source
53
+ end
54
+ else
55
+ requirements[key] = Regexp.escape(value)
56
+ end
83
57
  end
58
+ requirements.default ||= separators.any? ?
59
+ "[^#{separators.join}]+" : '.+'
60
+ requirements
84
61
  end
85
- re << scanner.rest
86
- str.replace(re)
87
- end
88
62
 
89
- def regexp_has_modifiers?(regexp)
90
- regexp.options & (Regexp::IGNORECASE | Regexp::EXTENDED) != 0
91
- end
63
+ def regexp_has_modifiers?(regexp)
64
+ regexp.options & (Regexp::IGNORECASE | Regexp::EXTENDED) != 0
65
+ end
66
+ end
92
67
  end
93
68
  end