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
@@ -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
@@ -39,20 +32,40 @@ module Rack::Mount
39
32
  obj.segments
40
33
  end
41
34
 
35
+ def defaults=(defaults)
36
+ @required_captures = nil
37
+ @required_params = nil
38
+ @required_defaults = nil
39
+ @defaults = defaults
40
+ end
41
+
42
+ def defaults
43
+ @defaults ||= {}
44
+ end
45
+
42
46
  def generatable?
43
47
  segments.any?
44
48
  end
45
49
 
46
- def generate(params = {}, recall = {}, defaults = {})
50
+ def generate(params = {}, recall = {}, options = {})
51
+ return nil unless generatable?
52
+
47
53
  merged = recall.merge(params)
48
- generate_from_segments(segments, params, merged, defaults)
54
+ return nil unless required_params.all? { |p| merged.include?(p) }
55
+ return nil unless required_defaults.all? { |k, v| merged[k] == v }
56
+
57
+ generate_from_segments(segments, params, merged, options)
49
58
  end
50
59
 
51
60
  def segments
52
61
  @segments ||= begin
53
- parse_segments(Utils.extract_regexp_parts(self))
54
- rescue ArgumentError
55
- Const::EMPTY_ARRAY
62
+ defaults
63
+ segments = []
64
+ catch(:halt) do
65
+ expression = Utils.parse_regexp(self)
66
+ segments = parse_segments(expression)
67
+ end
68
+ segments
56
69
  end
57
70
  end
58
71
 
@@ -61,60 +74,86 @@ module Rack::Mount
61
74
  end
62
75
 
63
76
  def required_captures
64
- segments.find_all { |s| s.is_a?(DynamicSegment) }
77
+ @required_captures ||= segments.find_all { |s|
78
+ s.is_a?(DynamicSegment) && !@defaults.include?(s.name)
79
+ }.freeze
80
+ end
81
+
82
+ def required_params
83
+ @required_params ||= required_captures.map { |s| s.name }.freeze
84
+ end
85
+
86
+ def required_defaults
87
+ @required_defaults ||= begin
88
+ required_defaults = @defaults.dup
89
+ captures.inject({}) { |h, s| h.merge!(s.to_hash) }.keys.each { |name|
90
+ required_defaults.delete(name)
91
+ }
92
+ required_defaults
93
+ end
94
+ end
95
+
96
+ def freeze
97
+ segments
98
+ captures
99
+ required_captures
100
+ required_params
101
+ required_defaults
102
+ super
65
103
  end
66
104
 
67
105
  private
68
106
  def parse_segments(segments)
69
107
  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)
108
+ segments.each_with_index do |part, index|
109
+ case part
110
+ when Regin::Anchor
111
+ # ignore
112
+ when Regin::Character
113
+ throw :halt unless part.literal?
114
+
115
+ if s.last.is_a?(String)
116
+ s.last << part.value.dup
78
117
  else
79
- s << parse_segments(part)
118
+ s << part.value.dup
80
119
  end
81
- else
82
- part = part.gsub('\\/', '/')
83
- static = Utils.extract_static_regexp(part)
84
- if static.is_a?(String)
85
- s << static.freeze
120
+ when Regin::Group
121
+ if part.name
122
+ s << DynamicSegment.new(part.name, part.expression.to_regexp(true))
86
123
  else
87
- raise ArgumentError, "failed to parse #{part.inspect}"
124
+ s << parse_segments(part.expression)
88
125
  end
126
+ when Regin::Expression
127
+ return parse_segments(part)
128
+ else
129
+ throw :halt
89
130
  end
90
131
  end
91
132
 
92
- s.freeze
133
+ s
93
134
  end
94
135
 
95
- def generate_from_segments(segments, params, merged, defaults, optional = false)
136
+ EMPTY_STRING = ''.freeze
137
+
138
+ def generate_from_segments(segments, params, merged, options, optional = false)
96
139
  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)
140
+ return EMPTY_STRING if segments.all? { |s| s.is_a?(String) }
141
+ return EMPTY_STRING unless segments.flatten.any? { |s|
142
+ params.has_key?(s.name) if s.is_a?(DynamicSegment)
100
143
  }
101
- return Const::EMPTY_STRING if segments.any? { |segment|
144
+ return EMPTY_STRING if segments.any? { |segment|
102
145
  if segment.is_a?(DynamicSegment)
103
- value = merged[segment.name] || defaults[segment.name]
104
- value = value.to_param if value.respond_to?(:to_param)
105
-
106
- merged_value = merged[segment.name]
107
- merged_value = merged_value.to_param if merged_value.respond_to?(:to_param)
146
+ value = merged[segment.name] || @defaults[segment.name]
147
+ value = parameterize(segment.name, value, options)
108
148
 
109
- default_value = defaults[segment.name]
110
- default_value = default_value.to_param if default_value.respond_to?(:to_param)
149
+ merged_value = parameterize(segment.name, merged[segment.name], options)
150
+ default_value = parameterize(segment.name, @defaults[segment.name], options)
111
151
 
112
152
  if value.nil? || segment !~ value
113
153
  true
114
154
  elsif merged_value == default_value
115
155
  # Nasty control flow
116
156
  return :clear_remaining_segments
117
- true
118
157
  else
119
158
  false
120
159
  end
@@ -127,20 +166,20 @@ module Rack::Mount
127
166
  when String
128
167
  segment
129
168
  when DynamicSegment
130
- value = params[segment.name] || merged[segment.name] || defaults[segment.name]
131
- value = value.to_param if value.respond_to?(:to_param)
169
+ value = params[segment.name] || merged[segment.name] || @defaults[segment.name]
170
+ value = parameterize(segment.name, value, options)
132
171
  if value && segment =~ value.to_s
133
172
  value
134
173
  else
135
174
  return
136
175
  end
137
176
  when Array
138
- value = generate_from_segments(segment, params, merged, defaults, true)
177
+ value = generate_from_segments(segment, params, merged, options, true)
139
178
  if value == :clear_remaining_segments
140
179
  segment.each { |s| params.delete(s.name) if s.is_a?(DynamicSegment) }
141
- Const::EMPTY_STRING
180
+ EMPTY_STRING
142
181
  elsif value.nil?
143
- Const::EMPTY_STRING
182
+ EMPTY_STRING
144
183
  else
145
184
  value
146
185
  end
@@ -152,6 +191,14 @@ module Rack::Mount
152
191
 
153
192
  generated.join
154
193
  end
194
+
195
+ def parameterize(name, value, options)
196
+ if block = options[:parameterize]
197
+ block.call(name, value)
198
+ else
199
+ value
200
+ end
201
+ end
155
202
  end
156
203
  include InstanceMethods
157
204
 
@@ -7,22 +7,6 @@ end
7
7
 
8
8
  module Rack::Mount
9
9
  class Multimap < NestedMultimap #:nodoc:
10
- def self.[](*args)
11
- map = super
12
- map.instance_variable_set('@fuzz', {})
13
- map
14
- end
15
-
16
- def initialize(default = [])
17
- @fuzz = {}
18
- super
19
- end
20
-
21
- def initialize_copy(original)
22
- @fuzz = original.instance_variable_get('@fuzz').dup
23
- super
24
- end
25
-
26
10
  def store(*args)
27
11
  keys = args.dup
28
12
  value = keys.pop
@@ -35,12 +19,11 @@ module Rack::Mount
35
19
  end
36
20
 
37
21
  if key.is_a?(Regexp)
38
- @fuzz[value] = key
39
22
  if keys.empty?
40
- hash_each_pair { |k, l| l << value if k =~ key }
23
+ @hash.each_pair { |k, l| l << value if k =~ key }
41
24
  self.default << value
42
25
  else
43
- hash_each_pair { |k, _|
26
+ @hash.each_pair { |k, _|
44
27
  if k =~ key
45
28
  args[0] = k
46
29
  super(*args)
@@ -56,12 +39,6 @@ module Rack::Mount
56
39
  end
57
40
  alias_method :[]=, :store
58
41
 
59
- def freeze
60
- @fuzz.clear
61
- @fuzz = nil
62
- super
63
- end
64
-
65
42
  undef :index, :invert
66
43
 
67
44
  def height
@@ -72,23 +49,5 @@ module Rack::Mount
72
49
  lengths = containers_with_default.map { |e| e.length }
73
50
  lengths.inject(0) { |sum, len| sum += len }.to_f / lengths.size
74
51
  end
75
-
76
- protected
77
- def update_container(key) #:nodoc:
78
- super do |container|
79
- if container.is_a?(self.class)
80
- container.each_container_with_default do |c|
81
- c.delete_if do |value|
82
- (requirement = @fuzz[value]) && key !~ requirement
83
- end
84
- end
85
- else
86
- container.delete_if do |value|
87
- (requirement = @fuzz[value]) && key !~ requirement
88
- end
89
- end
90
- yield container
91
- end
92
- end
93
52
  end
94
53
  end
@@ -2,6 +2,11 @@ require 'rack/mount/utils'
2
2
 
3
3
  module Rack::Mount
4
4
  class Prefix #:nodoc:
5
+ EMPTY_STRING = ''.freeze
6
+ PATH_INFO = 'PATH_INFO'.freeze
7
+ SCRIPT_NAME = 'SCRIPT_NAME'.freeze
8
+ SLASH = '/'.freeze
9
+
5
10
  KEY = 'rack.mount.prefix'.freeze
6
11
 
7
12
  def initialize(app, prefix = nil)
@@ -11,17 +16,17 @@ module Rack::Mount
11
16
 
12
17
  def call(env)
13
18
  if prefix = env[KEY] || @prefix
14
- old_path_info = env[Const::PATH_INFO].dup
15
- old_script_name = env[Const::SCRIPT_NAME].dup
19
+ old_path_info = env[PATH_INFO].dup
20
+ old_script_name = env[SCRIPT_NAME].dup
16
21
 
17
22
  begin
18
- env[Const::PATH_INFO] = Utils.normalize_path(env[Const::PATH_INFO].sub(prefix, Const::EMPTY_STRING))
19
- env[Const::PATH_INFO] = Const::EMPTY_STRING if env[Const::PATH_INFO] == Const::SLASH
20
- env[Const::SCRIPT_NAME] = Utils.normalize_path(env[Const::SCRIPT_NAME].to_s + prefix)
23
+ env[PATH_INFO] = env[PATH_INFO].sub(prefix, EMPTY_STRING)
24
+ env[PATH_INFO] = EMPTY_STRING if env[PATH_INFO] == SLASH
25
+ env[SCRIPT_NAME] = Utils.normalize_path(env[SCRIPT_NAME].to_s + prefix)
21
26
  @app.call(env)
22
27
  ensure
23
- env[Const::PATH_INFO] = old_path_info
24
- env[Const::SCRIPT_NAME] = old_script_name
28
+ env[PATH_INFO] = old_path_info
29
+ env[SCRIPT_NAME] = old_script_name
25
30
  end
26
31
  else
27
32
  @app.call(env)
@@ -1,7 +1,9 @@
1
- require 'rack/mount/utils'
2
-
3
1
  module Rack::Mount
4
- unless Const::SUPPORTS_NAMED_CAPTURES
2
+ if Regin.regexp_supports_named_captures?
3
+ RegexpWithNamedGroups = Regexp
4
+ else
5
+ require 'strscan'
6
+
5
7
  # A wrapper that adds shim named capture support to older
6
8
  # versions of Ruby.
7
9
  #
@@ -26,9 +28,25 @@ module Rack::Mount
26
28
 
27
29
  # Wraps Regexp with named capture support.
28
30
  def initialize(regexp)
29
- regexp, @names = Utils.extract_named_captures(regexp)
31
+ regexp = Regexp.compile(regexp) unless regexp.is_a?(Regexp)
32
+ source, options = regexp.source, regexp.options
33
+ @names, scanner = [], StringScanner.new(source)
34
+
35
+ while scanner.skip_until(/\(/)
36
+ if scanner.scan(/\?:<([^>]+)>/)
37
+ @names << scanner[1]
38
+ elsif scanner.scan(/\?(i?m?x?\-?i?m?x?)?:/)
39
+ # ignore noncapture
40
+ else
41
+ @names << nil
42
+ end
43
+ end
44
+ source.gsub!(/\?:<([^>]+)>/, '')
45
+
46
+ @names = [] unless @names.any?
30
47
  @names.freeze
31
- super(regexp)
48
+
49
+ super(source, options)
32
50
  end
33
51
 
34
52
  def names
@@ -42,8 +60,10 @@ module Rack::Mount
42
60
  }
43
61
  named_captures
44
62
  end
63
+
64
+ def eql?(other)
65
+ super && @names.eql?(other.names)
66
+ end
45
67
  end
46
- else
47
- RegexpWithNamedGroups = Regexp
48
68
  end
49
69
  end
@@ -9,11 +9,6 @@ module Rack::Mount
9
9
  # new Route objects. Instead use the factory method, RouteSet#add_route
10
10
  # to create new routes and add them to the set.
11
11
  class Route
12
- extend Mixover
13
-
14
- # Include generation and recognition concerns
15
- include Generation::Route, Recognition::Route
16
-
17
12
  # Valid rack application to call if conditions are met
18
13
  attr_reader :app
19
14
 
@@ -27,41 +22,107 @@ module Rack::Mount
27
22
  # Symbol identifier for the route used with named route generations
28
23
  attr_reader :name
29
24
 
30
- def initialize(set, app, conditions, defaults, name)
31
- @set = set
25
+ attr_reader :named_captures
32
26
 
27
+ def initialize(app, conditions, defaults, name)
33
28
  unless app.respond_to?(:call)
34
29
  raise ArgumentError, 'app must be a valid rack application' \
35
30
  ' and respond to call'
36
31
  end
37
32
  @app = app
38
33
 
39
- @name = name.to_sym if name
34
+ @name = name ? name.to_sym : nil
40
35
  @defaults = (defaults || {}).freeze
41
36
 
42
- unless conditions.is_a?(Hash)
43
- raise ArgumentError, 'conditions must be a Hash'
44
- end
45
37
  @conditions = {}
46
38
 
47
39
  conditions.each do |method, pattern|
48
40
  next unless method && pattern
49
41
 
50
- unless @set.valid_conditions.include?(method)
51
- raise ArgumentError, 'conditions may only include ' +
52
- @set.valid_conditions.inspect
42
+ pattern = Regexp.compile("\\A#{Regexp.escape(pattern)}\\Z") if pattern.is_a?(String)
43
+
44
+ if pattern.is_a?(Regexp)
45
+ pattern = Utils.normalize_extended_expression(pattern)
46
+ pattern = RegexpWithNamedGroups.new(pattern)
47
+ pattern.extend(GeneratableRegexp::InstanceMethods)
48
+ pattern.defaults = @defaults
53
49
  end
54
50
 
55
- pattern = Regexp.compile("\\A#{Regexp.escape(pattern)}\\Z") if pattern.is_a?(String)
56
- pattern = Utils.normalize_extended_expression(pattern)
57
- pattern = RegexpWithNamedGroups.new(pattern)
58
- pattern.extend(GeneratableRegexp::InstanceMethods)
59
51
  @conditions[method] = pattern.freeze
60
52
  end
61
53
 
54
+ @named_captures = {}
55
+ @conditions.map { |method, condition|
56
+ next unless condition.respond_to?(:named_captures)
57
+ @named_captures[method] = condition.named_captures.inject({}) { |named_captures, (k, v)|
58
+ named_captures[k.to_sym] = v.last - 1
59
+ named_captures
60
+ }.freeze
61
+ }
62
+ @named_captures.freeze
63
+
64
+ @has_significant_params = @conditions.any? { |method, condition|
65
+ (condition.respond_to?(:required_params) && condition.required_params.any?) ||
66
+ (condition.respond_to?(:required_defaults) && condition.required_defaults.any?)
67
+ }
68
+
69
+ if @conditions.has_key?(:path_info) &&
70
+ !Utils.regexp_anchored?(@conditions[:path_info])
71
+ @prefix = true
72
+ @app = Prefix.new(@app)
73
+ else
74
+ @prefix = false
75
+ end
76
+
62
77
  @conditions.freeze
63
78
  end
64
79
 
80
+ def prefix?
81
+ @prefix
82
+ end
83
+
84
+
85
+ def generation_keys
86
+ @conditions.inject({}) { |keys, (method, condition)|
87
+ if condition.respond_to?(:required_defaults)
88
+ keys.merge!(condition.required_defaults)
89
+ else
90
+ keys
91
+ end
92
+ }
93
+ end
94
+
95
+ def significant_params?
96
+ @has_significant_params
97
+ end
98
+
99
+ def generate(method, params = {}, recall = {}, options = {})
100
+ if method.nil?
101
+ result = @conditions.inject({}) { |h, (m, condition)|
102
+ if condition.respond_to?(:generate)
103
+ h[m] = condition.generate(params, recall, options)
104
+ end
105
+ h
106
+ }
107
+ return nil if result.values.compact.empty?
108
+ else
109
+ if condition = @conditions[method]
110
+ if condition.respond_to?(:generate)
111
+ result = condition.generate(params, recall, options)
112
+ end
113
+ end
114
+ end
115
+
116
+ if result
117
+ @defaults.each do |key, value|
118
+ params.delete(key) if params[key] == value
119
+ end
120
+ end
121
+
122
+ result
123
+ end
124
+
125
+
65
126
  def inspect #:nodoc:
66
127
  "#<#{self.class.name} @app=#{@app.inspect} @conditions=#{@conditions.inspect} @defaults=#{@defaults.inspect} @name=#{@name.inspect}>"
67
128
  end