rack-mount 0.0.1 → 0.0.2

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