rack-mount 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. data/README.rdoc +1 -1
  2. data/lib/rack/mount.rb +0 -2
  3. data/lib/rack/mount/analysis/frequency.rb +10 -3
  4. data/lib/rack/mount/analysis/splitting.rb +79 -62
  5. data/lib/rack/mount/generatable_regexp.rb +48 -45
  6. data/lib/rack/mount/generation/route.rb +10 -5
  7. data/lib/rack/mount/generation/route_set.rb +23 -31
  8. data/lib/rack/mount/prefix.rb +14 -15
  9. data/lib/rack/mount/recognition/code_generation.rb +51 -61
  10. data/lib/rack/mount/recognition/route.rb +7 -22
  11. data/lib/rack/mount/recognition/route_set.rb +40 -16
  12. data/lib/rack/mount/regexp_with_named_groups.rb +33 -7
  13. data/lib/rack/mount/route_set.rb +7 -5
  14. data/lib/rack/mount/strexp.rb +11 -40
  15. data/lib/rack/mount/strexp/parser.rb +158 -0
  16. data/lib/rack/mount/strexp/tokenizer.rb +84 -0
  17. data/lib/rack/mount/utils.rb +26 -163
  18. data/lib/rack/mount/vendor/multimap/multimap.rb +1 -0
  19. data/lib/rack/mount/vendor/reginald/reginald.rb +55 -0
  20. data/lib/rack/mount/vendor/reginald/reginald/alternation.rb +50 -0
  21. data/lib/rack/mount/vendor/reginald/reginald/anchor.rb +20 -0
  22. data/lib/rack/mount/vendor/reginald/reginald/character.rb +53 -0
  23. data/lib/rack/mount/vendor/reginald/reginald/character_class.rb +61 -0
  24. data/lib/rack/mount/vendor/reginald/reginald/expression.rb +75 -0
  25. data/lib/rack/mount/vendor/reginald/reginald/group.rb +61 -0
  26. data/lib/rack/mount/vendor/reginald/reginald/parser.rb +306 -0
  27. data/lib/rack/mount/vendor/reginald/reginald/tokenizer.rb +141 -0
  28. metadata +14 -15
  29. data/lib/rack/mount/const.rb +0 -45
  30. data/lib/rack/mount/meta_method.rb +0 -104
@@ -21,7 +21,7 @@ The API is extremely minimal and only 3 methods are exposed as the public API.
21
21
  # add_route takes a rack application and conditions to match with
22
22
  # conditions may be strings or regexps
23
23
  # See Rack::Mount::RouteSet#add_route for more options.
24
- set.add_route FooApp, :method => 'get' :path => %{/foo}
24
+ set.add_route FooApp, :method => 'get', :path => %{/foo}
25
25
  end
26
26
 
27
27
  # The route set itself is a simple rack app you mount
@@ -2,9 +2,7 @@ require 'rack'
2
2
 
3
3
  module Rack #:nodoc:
4
4
  module Mount #:nodoc:
5
- autoload :Const, 'rack/mount/const'
6
5
  autoload :GeneratableRegexp, 'rack/mount/generatable_regexp'
7
- autoload :MetaMethod, 'rack/mount/meta_method'
8
6
  autoload :Mixover, 'rack/mount/mixover'
9
7
  autoload :Multimap, 'rack/mount/multimap'
10
8
  autoload :Prefix, 'rack/mount/prefix'
@@ -1,3 +1,5 @@
1
+ require 'rack/mount/utils'
2
+
1
3
  module Rack::Mount
2
4
  module Analysis
3
5
  class Frequency #:nodoc:
@@ -33,10 +35,15 @@ module Rack::Mount
33
35
 
34
36
  def process_key(requirements, method, requirement)
35
37
  if requirement.is_a?(Regexp)
36
- requirements[method] = Utils.extract_static_regexp(requirement)
37
- else
38
- requirements[method] = requirement
38
+ expression = Utils.parse_regexp(requirement)
39
+ expression.reject! { |e| e.is_a?(Reginald::Anchor) }
40
+
41
+ if expression.is_a?(Reginald::Expression) && expression.literal?
42
+ return requirements[method] = expression.to_s
43
+ end
39
44
  end
45
+
46
+ requirements[method] = requirement
40
47
  end
41
48
 
42
49
  def report
@@ -1,6 +1,10 @@
1
+ require 'rack/mount/utils'
2
+
1
3
  module Rack::Mount
2
4
  module Analysis
3
5
  module Splitting
6
+ NULL = "\0".freeze
7
+
4
8
  class Key < Array
5
9
  def initialize(method, index, separators)
6
10
  replace([method, index, separators])
@@ -8,8 +12,8 @@ module Rack::Mount
8
12
 
9
13
  def self.split(value, separator_pattern)
10
14
  keys = value.split(separator_pattern)
11
- keys.shift if keys[0] == Const::EMPTY_STRING
12
- keys << Const::NULL
15
+ keys.shift if keys[0] == ''
16
+ keys << NULL
13
17
  keys
14
18
  end
15
19
 
@@ -53,93 +57,106 @@ module Rack::Mount
53
57
  def analyze_capture_boundaries(regexp, boundaries) #:nodoc:
54
58
  return boundaries unless regexp.is_a?(Regexp)
55
59
 
56
- parts = Utils.extract_regexp_parts(regexp) rescue []
60
+ parts = Utils.parse_regexp(regexp)
57
61
  parts.each_with_index do |part, index|
58
- break if part == Const::NULL
59
-
60
- if index > 0
61
- previous = parts[index-1]
62
- if previous.is_a?(Utils::Capture)
63
- previous = Utils.extract_static_regexp(previous.last_part) rescue nil
62
+ if part.is_a?(Reginald::Group)
63
+ if index > 0
64
+ previous = parts[index-1]
65
+ if previous.is_a?(Reginald::Character)
66
+ boundaries << previous.to_str
67
+ end
64
68
  end
65
- boundaries << previous[-1..-1] if previous.is_a?(String)
66
- end
67
69
 
68
- if index < parts.length
69
- following = parts[index+1]
70
- if following.is_a?(Utils::Capture)
71
- following = Utils.extract_static_regexp(following.first_part) rescue nil
70
+ if inside = part[0][0]
71
+ if inside.is_a?(Reginald::Character)
72
+ boundaries << inside.to_str
73
+ end
72
74
  end
73
- if following.is_a?(String) && following != Const::NULL
74
- boundaries << following[0..0] == '\\' ? following[1..1] : following[0..0]
75
+
76
+ if index < parts.length
77
+ following = parts[index+1]
78
+ if following.is_a?(Reginald::Character)
79
+ boundaries << following.to_str
80
+ end
75
81
  end
76
82
  end
77
83
  end
84
+
78
85
  boundaries
79
86
  end
80
87
 
81
88
  def generate_split_keys(regexp, separators) #:nodoc:
82
- escaped_separators = separators.map { |s| Regexp.escape(s) }
83
- separators_regexp = Regexp.union(*escaped_separators)
84
- segments, previous = [], nil
85
- regexp_options = regexp.options
86
-
87
- begin
88
- Utils.extract_regexp_parts(regexp).each do |part|
89
- if part.respond_to?(:optional?) && part.optional?
90
- if escaped_separators.include?(part.first)
91
- append_to_segments!(segments, previous, separators, regexp_options)
92
- end
93
-
94
- raise ArgumentError
89
+ segments = []
90
+ buf = nil
91
+ casefold = regexp.casefold?
92
+ parts = Utils.parse_regexp(regexp)
93
+ parts.each_with_index do |part, index|
94
+ case part
95
+ when Reginald::Anchor
96
+ if part.value == '$' || part.value == '\Z'
97
+ segments << join_buffer(buf, regexp) if buf
98
+ segments << NULL
99
+ buf = nil
100
+ break
95
101
  end
96
-
97
- append_to_segments!(segments, previous, separators, regexp_options)
98
- previous = nil
99
-
100
- if part == Const::NULL
101
- segments << Const::NULL
102
- raise ArgumentError
102
+ when Reginald::Character
103
+ if separators.any? { |s| part.include?(s) }
104
+ segments << join_buffer(buf, regexp) if buf
105
+ peek = parts[index+1]
106
+ if peek.is_a?(Reginald::Character) && separators.include?(peek)
107
+ segments << ''
108
+ end
109
+ buf = nil
110
+ else
111
+ buf ||= Reginald::Expression.new([])
112
+ buf << part
103
113
  end
104
-
105
- if part.is_a?(Utils::Capture)
106
- source = part.map { |p| p.to_s }.join
107
- append_to_segments!(segments, source, separators, regexp_options)
114
+ when Reginald::Group
115
+ if part.quantifier == '?'
116
+ value = part.expression.first
117
+ if separators.any? { |s| value.include?(s) }
118
+ segments << join_buffer(buf, regexp) if buf
119
+ buf = nil
120
+ end
121
+ break
122
+ elsif part.quantifier == nil
123
+ break if separators.any? { |s| part.include?(s) }
124
+ buf = nil
125
+ segments << part.to_regexp
108
126
  else
109
- parts = part.split(separators_regexp)
110
- parts.shift if parts[0] == Const::EMPTY_STRING
111
- previous = parts.pop
112
- parts.each { |p| append_to_segments!(segments, p, separators, regexp_options) }
127
+ break
113
128
  end
129
+ when Reginald::CharacterClass
130
+ break if separators.any? { |s| part.include?(s) }
131
+ buf = nil
132
+ segments << part.to_regexp
133
+ else
134
+ break
114
135
  end
115
136
 
116
- append_to_segments!(segments, previous, separators, regexp_options)
117
- rescue ArgumentError
118
- # generation failed somewhere, but lets take what we can get
137
+ if index + 1 == parts.size
138
+ segments << join_buffer(buf, regexp) if buf
139
+ buf = nil
140
+ break
141
+ end
119
142
  end
120
143
 
121
144
  while segments.length > 0 && (segments.last.nil? || segments.last == '')
122
145
  segments.pop
123
146
  end
124
147
 
148
+ segments.shift if segments[0].nil? || segments[0] == ''
149
+
125
150
  segments
126
151
  end
127
152
 
128
- def append_to_segments!(segments, s, separators, regexp_options) #:nodoc:
129
- return unless s
130
- separators.each do |separator|
131
- if s.gsub(/\[[^\]]+\]/, '').include?(separator)
132
- raise ArgumentError
133
- end
134
-
135
- if Regexp.compile("\\A#{s}\\Z") =~ separator
136
- raise ArgumentError
137
- end
153
+ def join_buffer(parts, regexp)
154
+ if parts.literal? && !regexp.casefold?
155
+ parts.to_s
156
+ else
157
+ parts.to_regexp
138
158
  end
139
-
140
- static = Utils.extract_static_regexp(s, regexp_options)
141
- segments << (static.is_a?(String) ? static : static)
142
159
  end
143
- end
160
+ end
144
161
  end
145
162
  end
@@ -6,7 +6,7 @@ module Rack::Mount
6
6
  attr_reader :name, :requirement
7
7
 
8
8
  def initialize(name, requirement)
9
- @name, @requirement = name.to_sym, bound_expression(requirement)
9
+ @name, @requirement = name.to_sym, requirement
10
10
  freeze
11
11
  end
12
12
 
@@ -25,13 +25,6 @@ module Rack::Mount
25
25
  def inspect
26
26
  "/(?<#{@name}>#{@requirement.source})/"
27
27
  end
28
-
29
- private
30
- def bound_expression(regexp)
31
- source, options = regexp.source, regexp.options
32
- source = "\\A#{source}\\Z"
33
- Regexp.compile(source, options).freeze
34
- end
35
28
  end
36
29
 
37
30
  module InstanceMethods
@@ -43,16 +36,19 @@ module Rack::Mount
43
36
  segments.any?
44
37
  end
45
38
 
46
- def generate(params = {}, recall = {}, defaults = {})
39
+ def generate(params = {}, recall = {}, defaults = {}, options = {})
47
40
  merged = recall.merge(params)
48
- generate_from_segments(segments, params, merged, defaults)
41
+ generate_from_segments(segments, params, merged, defaults, options)
49
42
  end
50
43
 
51
44
  def segments
52
45
  @segments ||= begin
53
- parse_segments(Utils.extract_regexp_parts(self))
54
- rescue ArgumentError
55
- Const::EMPTY_ARRAY
46
+ segments = []
47
+ catch(:halt) do
48
+ expression = Utils.parse_regexp(self)
49
+ segments = parse_segments(expression)
50
+ end
51
+ segments
56
52
  end
57
53
  end
58
54
 
@@ -67,54 +63,53 @@ module Rack::Mount
67
63
  private
68
64
  def parse_segments(segments)
69
65
  s = []
70
- segments.each do |part|
71
- if part.is_a?(String) && part == Const::NULL
72
- return s
73
- elsif part.is_a?(Utils::Capture)
74
- if part.named?
75
- source = part.map { |p| p.to_s }.join
76
- requirement = Regexp.compile(source)
77
- s << DynamicSegment.new(part.name, requirement)
66
+ segments.each_with_index do |part, index|
67
+ case part
68
+ when Reginald::Anchor
69
+ # ignore
70
+ when Reginald::Character
71
+ if s.last.is_a?(String)
72
+ s.last << part.dup
78
73
  else
79
- s << parse_segments(part)
74
+ s << part.dup
80
75
  end
81
- else
82
- part = part.gsub('\\/', '/')
83
- static = Utils.extract_static_regexp(part)
84
- if static.is_a?(String)
85
- s << static.freeze
76
+ when Reginald::Group
77
+ if part.name
78
+ s << DynamicSegment.new(part.name, part.expression.to_regexp)
86
79
  else
87
- raise ArgumentError, "failed to parse #{part.inspect}"
80
+ s << parse_segments(part)
88
81
  end
82
+ when Reginald::Expression
83
+ return parse_segments(part)
84
+ else
85
+ throw :halt
89
86
  end
90
87
  end
91
88
 
92
- s.freeze
89
+ s
93
90
  end
94
91
 
95
- def generate_from_segments(segments, params, merged, defaults, optional = false)
92
+ EMPTY_STRING = ''.freeze
93
+
94
+ def generate_from_segments(segments, params, merged, defaults, options, optional = false)
96
95
  if optional
97
- return Const::EMPTY_STRING if segments.all? { |s| s.is_a?(String) }
98
- return Const::EMPTY_STRING unless segments.flatten.any? { |s|
99
- params[s.name] if s.is_a?(DynamicSegment)
96
+ return EMPTY_STRING if segments.all? { |s| s.is_a?(String) }
97
+ return EMPTY_STRING unless segments.flatten.any? { |s|
98
+ params.has_key?(s.name) if s.is_a?(DynamicSegment)
100
99
  }
101
- return Const::EMPTY_STRING if segments.any? { |segment|
100
+ return EMPTY_STRING if segments.any? { |segment|
102
101
  if segment.is_a?(DynamicSegment)
103
102
  value = merged[segment.name] || defaults[segment.name]
104
- value = value.to_param if value.respond_to?(:to_param)
103
+ value = parameterize(segment.name, value, options)
105
104
 
106
- merged_value = merged[segment.name]
107
- merged_value = merged_value.to_param if merged_value.respond_to?(:to_param)
108
-
109
- default_value = defaults[segment.name]
110
- default_value = default_value.to_param if default_value.respond_to?(:to_param)
105
+ merged_value = parameterize(segment.name, merged[segment.name], options)
106
+ default_value = parameterize(segment.name, defaults[segment.name], options)
111
107
 
112
108
  if value.nil? || segment !~ value
113
109
  true
114
110
  elsif merged_value == default_value
115
111
  # Nasty control flow
116
112
  return :clear_remaining_segments
117
- true
118
113
  else
119
114
  false
120
115
  end
@@ -128,19 +123,19 @@ module Rack::Mount
128
123
  segment
129
124
  when DynamicSegment
130
125
  value = params[segment.name] || merged[segment.name] || defaults[segment.name]
131
- value = value.to_param if value.respond_to?(:to_param)
126
+ value = parameterize(segment.name, value, options)
132
127
  if value && segment =~ value.to_s
133
128
  value
134
129
  else
135
130
  return
136
131
  end
137
132
  when Array
138
- value = generate_from_segments(segment, params, merged, defaults, true)
133
+ value = generate_from_segments(segment, params, merged, defaults, options, true)
139
134
  if value == :clear_remaining_segments
140
135
  segment.each { |s| params.delete(s.name) if s.is_a?(DynamicSegment) }
141
- Const::EMPTY_STRING
136
+ EMPTY_STRING
142
137
  elsif value.nil?
143
- Const::EMPTY_STRING
138
+ EMPTY_STRING
144
139
  else
145
140
  value
146
141
  end
@@ -152,6 +147,14 @@ module Rack::Mount
152
147
 
153
148
  generated.join
154
149
  end
150
+
151
+ def parameterize(name, value, options)
152
+ if block = options[:parameterize]
153
+ block.call(name, value)
154
+ else
155
+ value
156
+ end
157
+ end
155
158
  end
156
159
  include InstanceMethods
157
160
 
@@ -21,17 +21,22 @@ module Rack::Mount
21
21
  }
22
22
  @required_defaults[method].freeze
23
23
  end
24
+ @has_significant_params = (@required_params.any? { |k, v| v.any? } || @required_defaults.any? { |k, v| v.any? }) ? true : false
24
25
 
25
26
  @required_params.freeze
26
27
  @required_defaults.freeze
27
28
  @generation_keys.freeze
28
29
  end
29
30
 
30
- def generate(methods, params = {}, recall = {})
31
+ def significant_params?
32
+ @has_significant_params
33
+ end
34
+
35
+ def generate(methods, params = {}, recall = {}, options = {})
31
36
  if methods.is_a?(Array)
32
- result = methods.map { |m| generate_method(m, params, recall, @defaults) || (return nil) }
37
+ result = methods.map { |m| generate_method(m, params, recall, @defaults, options) || (return nil) }
33
38
  else
34
- result = generate_method(methods, params, recall, @defaults)
39
+ result = generate_method(methods, params, recall, @defaults, options)
35
40
  end
36
41
 
37
42
  if result
@@ -44,13 +49,13 @@ module Rack::Mount
44
49
  end
45
50
 
46
51
  private
47
- def generate_method(method, params, recall, defaults)
52
+ def generate_method(method, params, recall, defaults, options)
48
53
  merged = recall.merge(params)
49
54
  return nil unless condition = @conditions[method]
50
55
  return nil if condition.segments.empty?
51
56
  return nil unless @required_params[method].all? { |p| merged.include?(p) }
52
57
  return nil unless @required_defaults[method].all? { |k, v| merged[k] == v }
53
- condition.generate(params, recall, defaults)
58
+ condition.generate(params, recall, defaults, options)
54
59
  end
55
60
  end
56
61
  end
@@ -33,19 +33,18 @@ module Rack::Mount
33
33
  # url(:controller => "people", :action => "show", :id => "1")
34
34
  # # => "/people/1"
35
35
  def url(*args)
36
- named_route, params, recall = extract_params!(*args)
36
+ named_route, params, recall, options = extract_params!(*args)
37
37
 
38
- params = URISegment.wrap_values(params)
39
- recall = URISegment.wrap_values(recall)
38
+ options[:parameterize] ||= lambda { |name, value| Utils.escape_uri(value) }
40
39
 
41
- unless result = generate(:path_info, named_route, params, recall)
40
+ unless result = generate(:path_info, named_route, params, recall, options)
42
41
  return
43
42
  end
44
43
 
45
44
  uri, params = result
46
45
  params.each do |k, v|
47
- if v._value
48
- params[k] = v._value
46
+ if v
47
+ params[k] = v
49
48
  else
50
49
  params.delete(k)
51
50
  end
@@ -58,14 +57,14 @@ module Rack::Mount
58
57
  def generate(method, *args) #:nodoc:
59
58
  raise 'route set not finalized' unless @generation_graph
60
59
 
61
- named_route, params, recall = extract_params!(*args)
60
+ named_route, params, recall, options = extract_params!(*args)
62
61
  merged = recall.merge(params)
63
62
  route = nil
64
63
 
65
64
  if named_route
66
65
  if route = @named_routes[named_route.to_sym]
67
66
  recall = route.defaults.merge(recall)
68
- url = route.generate(method, params, recall)
67
+ url = route.generate(method, params, recall, options)
69
68
  [url, params]
70
69
  else
71
70
  raise RoutingError, "#{named_route} failed to generate from #{params.inspect}"
@@ -79,7 +78,8 @@ module Rack::Mount
79
78
  end
80
79
  }
81
80
  @generation_graph[*keys].each do |r|
82
- if url = r.generate(method, params, recall)
81
+ next unless r.significant_params?
82
+ if url = r.generate(method, params, recall, options)
83
83
  return [url, params]
84
84
  end
85
85
  end
@@ -103,6 +103,8 @@ module Rack::Mount
103
103
 
104
104
  def build_generation_graph
105
105
  build_nested_route_set(@generation_keys) { |k, i|
106
+ throw :skip unless @routes[i].significant_params?
107
+
106
108
  if k = @generation_key_analyzer.possible_keys[i][k]
107
109
  k.to_s
108
110
  else
@@ -117,10 +119,16 @@ module Rack::Mount
117
119
 
118
120
  def extract_params!(*args)
119
121
  case args.length
122
+ when 4
123
+ named_route, params, recall, options = args
120
124
  when 3
121
- named_route, params, recall = args
125
+ if args[0].is_a?(Hash)
126
+ params, recall, options = args
127
+ else
128
+ named_route, params, recall = args
129
+ end
122
130
  when 2
123
- if args[0].is_a?(Hash) && args[1].is_a?(Hash)
131
+ if args[0].is_a?(Hash)
124
132
  params, recall = args
125
133
  else
126
134
  named_route, params = args
@@ -136,27 +144,11 @@ module Rack::Mount
136
144
  end
137
145
 
138
146
  named_route ||= nil
139
- params ||= {}
140
- recall ||= {}
141
-
142
- [named_route, params.dup, recall.dup]
143
- end
144
-
145
- class URISegment < Struct.new(:_value)
146
- def self.wrap_values(hash)
147
- hash.inject({}) { |h, (k, v)| h[k] = new(v); h }
148
- end
147
+ params ||= {}
148
+ recall ||= {}
149
+ options ||= {}
149
150
 
150
- extend Forwardable
151
- def_delegators :_value, :==, :eql?, :hash
152
-
153
- def to_param
154
- @to_param ||= begin
155
- v = _value.respond_to?(:to_param) ? _value.to_param : _value
156
- Utils.escape_uri(v)
157
- end
158
- end
159
- alias_method :to_s, :to_param
151
+ [named_route, params.dup, recall.dup, options.dup]
160
152
  end
161
153
  end
162
154
  end