rack-mount 0.6.14 → 0.7.1

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.
data/lib/rack/mount.rb CHANGED
@@ -24,7 +24,6 @@ module Rack #:nodoc:
24
24
  autoload :Version, 'rack/mount/version'
25
25
 
26
26
  module Analysis #:nodoc:
27
- autoload :Frequency, 'rack/mount/analysis/frequency'
28
27
  autoload :Histogram, 'rack/mount/analysis/histogram'
29
28
  autoload :Splitting, 'rack/mount/analysis/splitting'
30
29
  end
@@ -2,8 +2,8 @@ require 'rack/mount/utils'
2
2
 
3
3
  module Rack::Mount
4
4
  module Analysis
5
- class Splitting < Frequency
6
- NULL = "\0".freeze
5
+ class Splitting
6
+ NULL = "\0"
7
7
 
8
8
  class Key < Struct.new(:method, :index, :separators)
9
9
  def self.split(value, separator_pattern)
@@ -26,26 +26,44 @@ module Rack::Mount
26
26
  end
27
27
  end
28
28
 
29
+ def initialize(*keys)
30
+ clear
31
+ keys.each { |key| self << key }
32
+ end
33
+
29
34
  def clear
30
- @boundaries = {}
31
- super
35
+ @raw_keys = []
36
+ @key_frequency = Analysis::Histogram.new
37
+ self
32
38
  end
33
39
 
34
40
  def <<(key)
35
- super
36
- key.each_pair do |k, v|
37
- analyze_capture_boundaries(v, @boundaries[k] ||= Histogram.new)
41
+ raise ArgumentError unless key.is_a?(Hash)
42
+ @raw_keys << key
43
+ nil
44
+ end
45
+
46
+ def possible_keys
47
+ @possible_keys ||= begin
48
+ @raw_keys.map do |key|
49
+ key.inject({}) { |requirements, (method, requirement)|
50
+ process_key(requirements, method, requirement)
51
+ requirements
52
+ }
53
+ end
38
54
  end
39
55
  end
40
56
 
41
- def separators(key)
42
- @separators ||= {}
43
- @separators[key] ||= lookup_separators(key)
57
+ def report
58
+ @report ||= begin
59
+ possible_keys.each { |keys| keys.each_pair { |key, _| @key_frequency << key } }
60
+ return [] if @key_frequency.count <= 1
61
+ @key_frequency.keys_in_upper_quartile
62
+ end
44
63
  end
45
- attr_writer :separators
46
64
 
47
- def lookup_separators(key)
48
- @boundaries[key].keys_in_upper_quartile
65
+ def expire!
66
+ @possible_keys = @report = nil
49
67
  end
50
68
 
51
69
  def process_key(requirements, method, requirement)
@@ -55,40 +73,22 @@ module Rack::Mount
55
73
  requirements[Key.new(method, index, Regexp.union(*separators))] = value
56
74
  end
57
75
  else
58
- super
59
- end
60
- end
61
-
62
- private
63
- def analyze_capture_boundaries(regexp, boundaries) #:nodoc:
64
- return boundaries unless regexp.is_a?(Regexp)
65
-
66
- parts = Utils.parse_regexp(regexp)
67
- parts.each_with_index do |part, index|
68
- if part.is_a?(Regin::Group)
69
- if index > 0
70
- previous = parts[index-1]
71
- if previous.is_a?(Regin::Character) && previous.literal?
72
- boundaries << previous.to_s
73
- end
74
- end
75
-
76
- if inside = part.expression[0]
77
- if inside.is_a?(Regin::Character) && inside.literal?
78
- boundaries << inside.to_s
79
- end
80
- end
76
+ if requirement.is_a?(Regexp)
77
+ expression = Utils.parse_regexp(requirement)
81
78
 
82
- if index < parts.length
83
- following = parts[index+1]
84
- if following.is_a?(Regin::Character) && following.literal?
85
- boundaries << following.to_s
86
- end
87
- end
79
+ if expression.is_a?(Regin::Expression) && expression.anchored_to_line?
80
+ expression = Regin::Expression.new(expression.reject { |e| e.is_a?(Regin::Anchor) })
81
+ return requirements[method] = expression.to_s if expression.literal?
88
82
  end
89
83
  end
90
84
 
91
- boundaries
85
+ requirements[method] = requirement
86
+ end
87
+ end
88
+
89
+ private
90
+ def separators(key)
91
+ key == :path_info ? ["/", "."] : []
92
92
  end
93
93
 
94
94
  def generate_split_keys(regexp, separators) #:nodoc:
@@ -1,12 +1,16 @@
1
- begin
2
- require 'nested_multimap'
3
- rescue LoadError
4
- $: << File.expand_path(File.join(File.dirname(__FILE__), 'vendor/multimap'))
5
- require 'nested_multimap'
6
- end
7
-
8
1
  module Rack::Mount
9
- class Multimap < NestedMultimap #:nodoc:
2
+ class Multimap #:nodoc:
3
+ def initialize(default = [])
4
+ @hash = Hash.new(default)
5
+ end
6
+
7
+ def initialize_copy(original)
8
+ @hash = Hash.new(original.default.dup)
9
+ original.hash.each_pair do |key, container|
10
+ @hash[key] = container.dup
11
+ end
12
+ end
13
+
10
14
  def store(*args)
11
15
  keys = args.dup
12
16
  value = keys.pop
@@ -26,7 +30,7 @@ module Rack::Mount
26
30
  @hash.each_pair { |k, _|
27
31
  if k =~ key
28
32
  args[0] = k
29
- super(*args)
33
+ _store(*args)
30
34
  end
31
35
  }
32
36
 
@@ -34,12 +38,19 @@ module Rack::Mount
34
38
  default[*keys.dup] = value
35
39
  end
36
40
  else
37
- super(*args)
41
+ _store(*args)
38
42
  end
39
43
  end
40
44
  alias_method :[]=, :store
41
45
 
42
- undef :index, :invert
46
+ def [](*keys)
47
+ i, l, r, k = 0, keys.length, self, self.class
48
+ while r.is_a?(k)
49
+ r = i < l ? r.hash[keys[i]] : r.default
50
+ i += 1
51
+ end
52
+ r
53
+ end
43
54
 
44
55
  def height
45
56
  containers_with_default.max { |a, b| a.length <=> b.length }.length
@@ -49,5 +60,78 @@ module Rack::Mount
49
60
  lengths = containers_with_default.map { |e| e.length }
50
61
  lengths.inject(0) { |sum, len| sum += len }.to_f / lengths.size
51
62
  end
63
+
64
+ def containers_with_default
65
+ containers = []
66
+ each_container_with_default { |container| containers << container }
67
+ containers
68
+ end
69
+
70
+ protected
71
+ def _store(*args)
72
+ keys = args
73
+ value = args.pop
74
+
75
+ raise ArgumentError, 'wrong number of arguments (1 for 2)' unless value
76
+
77
+ if keys.length > 1
78
+ update_container(keys.shift) do |container|
79
+ container = self.class.new(container) unless container.is_a?(self.class)
80
+ container[*keys] = value
81
+ container
82
+ end
83
+ elsif keys.length == 1
84
+ update_container(keys.first) do |container|
85
+ container << value
86
+ container
87
+ end
88
+ else
89
+ self << value
90
+ end
91
+ end
92
+
93
+ def <<(value)
94
+ @hash.each_value { |container| container << value }
95
+ self.default << value
96
+ self
97
+ end
98
+
99
+ def each_container_with_default(&block)
100
+ @hash.each_value do |container|
101
+ iterate_over_container(container, &block)
102
+ end
103
+ iterate_over_container(default, &block)
104
+ self
105
+ end
106
+
107
+ def default
108
+ @hash.default
109
+ end
110
+
111
+ def default=(value)
112
+ @hash.default = value
113
+ end
114
+
115
+ def hash
116
+ @hash
117
+ end
118
+
119
+ private
120
+ def update_container(key)
121
+ container = @hash[key]
122
+ container = container.dup if container.equal?(default)
123
+ container = yield(container)
124
+ @hash[key] = container
125
+ end
126
+
127
+ def iterate_over_container(container)
128
+ if container.respond_to?(:each_container_with_default)
129
+ container.each_container_with_default do |value|
130
+ yield value
131
+ end
132
+ else
133
+ yield container
134
+ end
135
+ end
52
136
  end
53
137
  end
@@ -25,7 +25,9 @@ module Rack::Mount
25
25
  @named_routes = {}
26
26
 
27
27
  @recognition_key_analyzer = Analysis::Splitting.new
28
- @generation_key_analyzer = Analysis::Frequency.new
28
+
29
+ @generation_keys = [:controller, :action]
30
+ @generation_route_keys = []
29
31
 
30
32
  @request_class = options.delete(:request_class) || Rack::Request
31
33
  @valid_conditions = @request_class.public_instance_methods.map! { |m| m.to_sym }
@@ -69,7 +71,7 @@ module Rack::Mount
69
71
  @recognition_key_analyzer << route.conditions
70
72
 
71
73
  @named_routes[route.name] = route if route.name
72
- @generation_key_analyzer << route.generation_keys
74
+ @generation_route_keys << route.generation_keys
73
75
 
74
76
  expire!
75
77
  route
@@ -249,7 +251,6 @@ module Rack::Mount
249
251
 
250
252
  @recognition_keys = build_recognition_keys
251
253
  @recognition_graph = build_recognition_graph
252
- @generation_keys = build_generation_keys
253
254
  @generation_graph = build_generation_graph
254
255
 
255
256
  self
@@ -263,7 +264,7 @@ module Rack::Mount
263
264
  rehash
264
265
 
265
266
  @recognition_key_analyzer = nil
266
- @generation_key_analyzer = nil
267
+ @generation_route_keys = nil
267
268
  @valid_conditions = nil
268
269
 
269
270
  @routes.each { |route| route.freeze }
@@ -273,26 +274,6 @@ module Rack::Mount
273
274
  super
274
275
  end
275
276
 
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
277
  protected
297
278
  def recognition_stats
298
279
  { :keys => @recognition_keys,
@@ -307,12 +288,7 @@ module Rack::Mount
307
288
  @recognition_keys = @recognition_graph = nil
308
289
  @recognition_key_analyzer.expire!
309
290
 
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]
291
+ @generation_graph = nil
316
292
  end
317
293
 
318
294
  # An internal helper method for constructing a nested set from
@@ -350,7 +326,7 @@ module Rack::Mount
350
326
  build_nested_route_set(@generation_keys) { |k, i|
351
327
  throw :skip unless @routes[i].significant_params?
352
328
 
353
- if k = @generation_key_analyzer.possible_keys[i][k]
329
+ if k = @generation_route_keys[i][k]
354
330
  k.to_s
355
331
  else
356
332
  nil
@@ -358,12 +334,6 @@ module Rack::Mount
358
334
  }
359
335
  end
360
336
 
361
- def build_generation_keys
362
- keys = @generation_key_analyzer.report
363
- Utils.debug "generation keys - #{keys.inspect}"
364
- keys
365
- end
366
-
367
337
  def extract_params!(*args)
368
338
  case args.length
369
339
  when 4
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-mount
3
3
  version: !ruby/object:Gem::Version
4
- hash: 27
4
+ hash: 1
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 6
9
- - 14
10
- version: 0.6.14
8
+ - 7
9
+ - 1
10
+ version: 0.7.1
11
11
  platform: ruby
12
12
  authors:
13
13
  - Joshua Peek
@@ -72,7 +72,6 @@ extra_rdoc_files: []
72
72
 
73
73
  files:
74
74
  - lib/rack/mount.rb
75
- - lib/rack/mount/analysis/frequency.rb
76
75
  - lib/rack/mount/analysis/histogram.rb
77
76
  - lib/rack/mount/analysis/splitting.rb
78
77
  - lib/rack/mount/code_generation.rb
@@ -88,9 +87,6 @@ files:
88
87
  - lib/rack/mount/strexp/tokenizer.rb
89
88
  - lib/rack/mount/strexp/tokenizer.rex
90
89
  - lib/rack/mount/utils.rb
91
- - lib/rack/mount/vendor/multimap/multimap.rb
92
- - lib/rack/mount/vendor/multimap/multiset.rb
93
- - lib/rack/mount/vendor/multimap/nested_multimap.rb
94
90
  - lib/rack/mount/vendor/regin/regin.rb
95
91
  - lib/rack/mount/vendor/regin/regin/alternation.rb
96
92
  - lib/rack/mount/vendor/regin/regin/anchor.rb
@@ -1,60 +0,0 @@
1
- require 'rack/mount/utils'
2
-
3
- module Rack::Mount
4
- module Analysis
5
- class Frequency #:nodoc:
6
- def initialize(*keys)
7
- clear
8
- keys.each { |key| self << key }
9
- end
10
-
11
- def clear
12
- @raw_keys = []
13
- @key_frequency = Analysis::Histogram.new
14
- self
15
- end
16
-
17
- def <<(key)
18
- raise ArgumentError unless key.is_a?(Hash)
19
- @raw_keys << key
20
- nil
21
- end
22
-
23
- def possible_keys
24
- @possible_keys ||= begin
25
- @raw_keys.map do |key|
26
- key.inject({}) { |requirements, (method, requirement)|
27
- process_key(requirements, method, requirement)
28
- requirements
29
- }
30
- end
31
- end
32
- end
33
-
34
- def process_key(requirements, method, requirement)
35
- if requirement.is_a?(Regexp)
36
- expression = Utils.parse_regexp(requirement)
37
-
38
- if expression.is_a?(Regin::Expression) && expression.anchored_to_line?
39
- expression = Regin::Expression.new(expression.reject { |e| e.is_a?(Regin::Anchor) })
40
- return requirements[method] = expression.to_s if expression.literal?
41
- end
42
- end
43
-
44
- requirements[method] = requirement
45
- end
46
-
47
- def report
48
- @report ||= begin
49
- possible_keys.each { |keys| keys.each_pair { |key, _| @key_frequency << key } }
50
- return [] if @key_frequency.count <= 1
51
- @key_frequency.keys_in_upper_quartile
52
- end
53
- end
54
-
55
- def expire!
56
- @possible_keys = @report = nil
57
- end
58
- end
59
- end
60
- end
@@ -1,569 +0,0 @@
1
- require 'forwardable'
2
- require 'multiset'
3
-
4
- # Multimap is a generalization of a map or associative array
5
- # abstract data type in which more than one value may be associated
6
- # with and returned for a given key.
7
- #
8
- # == Example
9
- #
10
- # require 'multimap'
11
- # map = Multimap.new
12
- # map["a"] = 100
13
- # map["b"] = 200
14
- # map["a"] = 300
15
- # map["a"] # -> [100, 300]
16
- # map["b"] # -> [200]
17
- # map.keys # -> #<Multiset: {a, a, b}>
18
- class Multimap
19
- extend Forwardable
20
-
21
- include Enumerable
22
-
23
- # call-seq:
24
- # Multimap[ [key =>|, value]* ] => multimap
25
- #
26
- # Creates a new multimap populated with the given objects.
27
- #
28
- # Multimap["a", 100, "b", 200] #=> {"a"=>[100], "b"=>[200]}
29
- # Multimap["a" => 100, "b" => 200] #=> {"a"=>[100], "b"=>[200]}
30
- def self.[](*args)
31
- default = []
32
-
33
- if args.size == 2 && args.last.is_a?(Hash)
34
- default = args.shift
35
- elsif !args.first.is_a?(Hash) && args.size % 2 == 1
36
- default = args.shift
37
- end
38
-
39
- if args.size == 1 && args.first.is_a?(Hash)
40
- args[0] = args.first.inject({}) { |hash, (key, value)|
41
- unless value.is_a?(default.class)
42
- value = (default.dup << value)
43
- end
44
- hash[key] = value
45
- hash
46
- }
47
- else
48
- index = 0
49
- args.map! { |value|
50
- unless index % 2 == 0 || value.is_a?(default.class)
51
- value = (default.dup << value)
52
- end
53
- index += 1
54
- value
55
- }
56
- end
57
-
58
- map = new
59
- map.instance_variable_set(:@hash, Hash[*args])
60
- map.default = default
61
- map
62
- end
63
-
64
- # call-seq:
65
- # Multimap.new => multimap
66
- # Multimap.new(default) => multimap
67
- #
68
- # Returns a new, empty multimap.
69
- #
70
- # map = Multimap.new(Set.new)
71
- # h["a"] = 100
72
- # h["b"] = 200
73
- # h["a"] #=> [100].to_set
74
- # h["c"] #=> [].to_set
75
- def initialize(default = [])
76
- @hash = Hash.new(default)
77
- end
78
-
79
- def initialize_copy(original) #:nodoc:
80
- @hash = Hash.new(original.default.dup)
81
- original._internal_hash.each_pair do |key, container|
82
- @hash[key] = container.dup
83
- end
84
- end
85
-
86
- def_delegators :@hash, :clear, :default, :default=, :empty?,
87
- :fetch, :has_key?, :key?
88
-
89
- # Retrieves the <i>value</i> object corresponding to the
90
- # <i>*keys</i> object.
91
- def [](key)
92
- @hash[key]
93
- end
94
-
95
- # call-seq:
96
- # map[key] = value => value
97
- # map.store(key, value) => value
98
- #
99
- # Associates the value given by <i>value</i> with the key
100
- # given by <i>key</i>. Unlike a regular hash, multiple can be
101
- # assoicated with the same value.
102
- #
103
- # map = Multimap["a" => 100, "b" => 200]
104
- # map["a"] = 9
105
- # map["c"] = 4
106
- # map #=> {"a" => [100, 9], "b" => [200], "c" => [4]}
107
- def store(key, value)
108
- update_container(key) do |container|
109
- container << value
110
- container
111
- end
112
- end
113
- alias_method :[]=, :store
114
-
115
- # call-seq:
116
- # map.delete(key, value) => value
117
- # map.delete(key) => value
118
- #
119
- # Deletes and returns a key-value pair from <i>map</i>. If only
120
- # <i>key</i> is given, all the values matching that key will be
121
- # deleted.
122
- #
123
- # map = Multimap["a" => 100, "b" => [200, 300]]
124
- # map.delete("b", 300) #=> 300
125
- # map.delete("a") #=> [100]
126
- def delete(key, value = nil)
127
- if value
128
- @hash[key].delete(value)
129
- else
130
- @hash.delete(key)
131
- end
132
- end
133
-
134
- # call-seq:
135
- # map.each { |key, value| block } => map
136
- #
137
- # Calls <i>block</i> for each key/value pair in <i>map</i>, passing
138
- # the key and value to the block as a two-element array.
139
- #
140
- # map = Multimap["a" => 100, "b" => [200, 300]]
141
- # map.each { |key, value| puts "#{key} is #{value}" }
142
- #
143
- # <em>produces:</em>
144
- #
145
- # a is 100
146
- # b is 200
147
- # b is 300
148
- def each
149
- each_pair do |key, value|
150
- yield [key, value]
151
- end
152
- end
153
-
154
- # call-seq:
155
- # map.each_association { |key, container| block } => map
156
- #
157
- # Calls <i>block</i> once for each key/container in <i>map</i>, passing
158
- # the key and container to the block as parameters.
159
- #
160
- # map = Multimap["a" => 100, "b" => [200, 300]]
161
- # map.each_association { |key, container| puts "#{key} is #{container}" }
162
- #
163
- # <em>produces:</em>
164
- #
165
- # a is [100]
166
- # b is [200, 300]
167
- def each_association(&block)
168
- @hash.each_pair(&block)
169
- end
170
-
171
- # call-seq:
172
- # map.each_container { |container| block } => map
173
- #
174
- # Calls <i>block</i> for each container in <i>map</i>, passing the
175
- # container as a parameter.
176
- #
177
- # map = Multimap["a" => 100, "b" => [200, 300]]
178
- # map.each_container { |container| puts container }
179
- #
180
- # <em>produces:</em>
181
- #
182
- # [100]
183
- # [200, 300]
184
- def each_container
185
- each_association do |_, container|
186
- yield container
187
- end
188
- end
189
-
190
- # call-seq:
191
- # map.each_key { |key| block } => map
192
- #
193
- # Calls <i>block</i> for each key in <i>hsh</i>, passing the key
194
- # as a parameter.
195
- #
196
- # map = Multimap["a" => 100, "b" => [200, 300]]
197
- # map.each_key { |key| puts key }
198
- #
199
- # <em>produces:</em>
200
- #
201
- # a
202
- # b
203
- # b
204
- def each_key
205
- each_pair do |key, _|
206
- yield key
207
- end
208
- end
209
-
210
- # call-seq:
211
- # map.each_pair { |key_value_array| block } => map
212
- #
213
- # Calls <i>block</i> for each key/value pair in <i>map</i>,
214
- # passing the key and value as parameters.
215
- #
216
- # map = Multimap["a" => 100, "b" => [200, 300]]
217
- # map.each_pair { |key, value| puts "#{key} is #{value}" }
218
- #
219
- # <em>produces:</em>
220
- #
221
- # a is 100
222
- # b is 200
223
- # b is 300
224
- def each_pair
225
- each_association do |key, values|
226
- values.each do |value|
227
- yield key, value
228
- end
229
- end
230
- end
231
-
232
- # call-seq:
233
- # map.each_value { |value| block } => map
234
- #
235
- # Calls <i>block</i> for each key in <i>map</i>, passing the
236
- # value as a parameter.
237
- #
238
- # map = Multimap["a" => 100, "b" => [200, 300]]
239
- # map.each_value { |value| puts value }
240
- #
241
- # <em>produces:</em>
242
- #
243
- # 100
244
- # 200
245
- # 300
246
- def each_value
247
- each_pair do |_, value|
248
- yield value
249
- end
250
- end
251
-
252
- def ==(other) #:nodoc:
253
- case other
254
- when Multimap
255
- @hash == other._internal_hash
256
- else
257
- @hash == other
258
- end
259
- end
260
-
261
- def eql?(other) #:nodoc:
262
- case other
263
- when Multimap
264
- @hash.eql?(other._internal_hash)
265
- else
266
- @hash.eql?(other)
267
- end
268
- end
269
-
270
- def freeze #:nodoc:
271
- each_container { |container| container.freeze }
272
- default.freeze
273
- super
274
- end
275
-
276
- # call-seq:
277
- # map.has_value?(value) => true or false
278
- # map.value?(value) => true or false
279
- #
280
- # Returns <tt>true</tt> if the given value is present for any key
281
- # in <i>map</i>.
282
- #
283
- # map = Multimap["a" => 100, "b" => [200, 300]]
284
- # map.has_value?(300) #=> true
285
- # map.has_value?(999) #=> false
286
- def has_value?(value)
287
- values.include?(value)
288
- end
289
- alias_method :value?, :has_value?
290
-
291
- # call-seq:
292
- # map.index(value) => key
293
- #
294
- # Returns the key for a given value. If not found, returns
295
- # <tt>nil</tt>.
296
- #
297
- # map = Multimap["a" => 100, "b" => [200, 300]]
298
- # map.index(100) #=> "a"
299
- # map.index(200) #=> "b"
300
- # map.index(999) #=> nil
301
- def index(value)
302
- invert[value]
303
- end
304
-
305
- # call-seq:
306
- # map.delete_if {| key, value | block } -> map
307
- #
308
- # Deletes every key-value pair from <i>map</i> for which <i>block</i>
309
- # evaluates to <code>true</code>.
310
- #
311
- # map = Multimap["a" => 100, "b" => [200, 300]]
312
- # map.delete_if {|key, value| value >= 300 }
313
- # #=> Multimap["a" => 100, "b" => 200]
314
- #
315
- def delete_if
316
- each_association do |key, container|
317
- container.delete_if do |value|
318
- yield [key, value]
319
- end
320
- end
321
- self
322
- end
323
-
324
- # call-seq:
325
- # map.reject {| key, value | block } -> map
326
- #
327
- # Same as <code>Multimap#delete_if</code>, but works on (and returns) a
328
- # copy of the <i>map</i>. Equivalent to
329
- # <code><i>map</i>.dup.delete_if</code>.
330
- #
331
- def reject(&block)
332
- dup.delete_if(&block)
333
- end
334
-
335
- # call-seq:
336
- # map.reject! {| key, value | block } -> map or nil
337
- #
338
- # Equivalent to <code>Multimap#delete_if</code>, but returns
339
- # <code>nil</code> if no changes were made.
340
- #
341
- def reject!(&block)
342
- old_size = size
343
- delete_if(&block)
344
- old_size == size ? nil : self
345
- end
346
-
347
- # call-seq:
348
- # map.replace(other_map) => map
349
- #
350
- # Replaces the contents of <i>map</i> with the contents of
351
- # <i>other_map</i>.
352
- #
353
- # map = Multimap["a" => 100, "b" => 200]
354
- # map.replace({ "c" => 300, "d" => 400 })
355
- # #=> Multimap["c" => 300, "d" => 400]
356
- def replace(other)
357
- case other
358
- when Array
359
- @hash.replace(self.class[self.default, *other])
360
- when Hash
361
- @hash.replace(self.class[self.default, other])
362
- when self.class
363
- @hash.replace(other)
364
- else
365
- raise ArgumentError
366
- end
367
- end
368
-
369
- # call-seq:
370
- # map.invert => multimap
371
- #
372
- # Returns a new multimap created by using <i>map</i>'s values as keys,
373
- # and the keys as values.
374
- #
375
- # map = Multimap["n" => 100, "m" => 100, "d" => [200, 300]]
376
- # map.invert #=> Multimap[100 => ["n", "m"], 200 => "d", 300 => "d"]
377
- def invert
378
- h = self.class.new(default.dup)
379
- each_pair { |key, value| h[value] = key }
380
- h
381
- end
382
-
383
- # call-seq:
384
- # map.keys => multiset
385
- #
386
- # Returns a new +Multiset+ populated with the keys from this hash. See also
387
- # <tt>Multimap#values</tt> and <tt>Multimap#containers</tt>.
388
- #
389
- # map = Multimap["a" => 100, "b" => [200, 300], "c" => 400]
390
- # map.keys #=> Multiset.new(["a", "b", "b", "c"])
391
- def keys
392
- keys = Multiset.new
393
- each_key { |key| keys << key }
394
- keys
395
- end
396
-
397
- # Returns true if the given key is present in Multimap.
398
- def include?(key)
399
- keys.include?(key)
400
- end
401
- alias_method :member?, :include?
402
-
403
- # call-seq:
404
- # map.length => fixnum
405
- # map.size => fixnum
406
- #
407
- # Returns the number of key-value pairs in the map.
408
- #
409
- # map = Multimap["a" => 100, "b" => [200, 300], "c" => 400]
410
- # map.length #=> 4
411
- # map.delete("a") #=> 100
412
- # map.length #=> 3
413
- def size
414
- values.size
415
- end
416
- alias_method :length, :size
417
-
418
- # call-seq:
419
- # map.merge(other_map) => multimap
420
- #
421
- # Returns a new multimap containing the contents of <i>other_map</i> and
422
- # the contents of <i>map</i>.
423
- #
424
- # map1 = Multimap["a" => 100, "b" => 200]
425
- # map2 = Multimap["a" => 254, "c" => 300]
426
- # map2.merge(map2) #=> Multimap["a" => 100, "b" => [200, 254], "c" => 300]
427
- # map1 #=> Multimap["a" => 100, "b" => 200]
428
- def merge(other)
429
- dup.update(other)
430
- end
431
-
432
- # call-seq:
433
- # map.merge!(other_map) => multimap
434
- # map.update(other_map) => multimap
435
- #
436
- # Adds each pair from <i>other_map</i> to <i>map</i>.
437
- #
438
- # map1 = Multimap["a" => 100, "b" => 200]
439
- # map2 = Multimap["b" => 254, "c" => 300]
440
- #
441
- # map1.merge!(map2)
442
- # #=> Multimap["a" => 100, "b" => [200, 254], "c" => 300]
443
- def update(other)
444
- case other
445
- when self.class
446
- other.each_pair { |key, value| store(key, value) }
447
- when Hash
448
- update(self.class[self.default, other])
449
- else
450
- raise ArgumentError
451
- end
452
- self
453
- end
454
- alias_method :merge!, :update
455
-
456
- # call-seq:
457
- # map.select { |key, value| block } => multimap
458
- #
459
- # Returns a new Multimap consisting of the pairs for which the
460
- # block returns true.
461
- #
462
- # map = Multimap["a" => 100, "b" => 200, "c" => 300]
463
- # map.select { |k,v| k > "a" } #=> Multimap["b" => 200, "c" => 300]
464
- # map.select { |k,v| v < 200 } #=> Multimap["a" => 100]
465
- def select
466
- inject(self.class.new) { |map, (key, value)|
467
- map[key] = value if yield([key, value])
468
- map
469
- }
470
- end
471
-
472
- # call-seq:
473
- # map.to_a => array
474
- #
475
- # Converts <i>map</i> to a nested array of [<i>key,
476
- # value</i>] arrays.
477
- #
478
- # map = Multimap["a" => 100, "b" => [200, 300], "c" => 400]
479
- # map.to_a #=> [["a", 100], ["b", 200], ["b", 300], ["c", 400]]
480
- def to_a
481
- ary = []
482
- each_pair do |key, value|
483
- ary << [key, value]
484
- end
485
- ary
486
- end
487
-
488
- # call-seq:
489
- # map.to_hash => hash
490
- #
491
- # Converts <i>map</i> to a basic hash.
492
- #
493
- # map = Multimap["a" => 100, "b" => [200, 300]]
494
- # map.to_hash #=> { "a" => [100], "b" => [200, 300] }
495
- def to_hash
496
- @hash.dup
497
- end
498
-
499
- # call-seq:
500
- # map.containers => array
501
- #
502
- # Returns a new array populated with the containers from <i>map</i>. See
503
- # also <tt>Multimap#keys</tt> and <tt>Multimap#values</tt>.
504
- #
505
- # map = Multimap["a" => 100, "b" => [200, 300]]
506
- # map.containers #=> [[100], [200, 300]]
507
- def containers
508
- containers = []
509
- each_container { |container| containers << container }
510
- containers
511
- end
512
-
513
- # call-seq:
514
- # map.values => array
515
- #
516
- # Returns a new array populated with the values from <i>map</i>. See
517
- # also <tt>Multimap#keys</tt> and <tt>Multimap#containers</tt>.
518
- #
519
- # map = Multimap["a" => 100, "b" => [200, 300]]
520
- # map.values #=> [100, 200, 300]
521
- def values
522
- values = []
523
- each_value { |value| values << value }
524
- values
525
- end
526
-
527
- # Return an array containing the values associated with the given keys.
528
- def values_at(*keys)
529
- @hash.values_at(*keys)
530
- end
531
-
532
- def marshal_dump #:nodoc:
533
- @hash
534
- end
535
-
536
- def marshal_load(hash) #:nodoc:
537
- @hash = hash
538
- end
539
-
540
- def to_yaml(opts = {}) #:nodoc:
541
- YAML::quick_emit(self, opts) do |out|
542
- out.map(taguri, to_yaml_style) do |map|
543
- @hash.each do |k, v|
544
- map.add(k, v)
545
- end
546
- map.add('__default__', @hash.default)
547
- end
548
- end
549
- end
550
-
551
- def yaml_initialize(tag, val) #:nodoc:
552
- default = val.delete('__default__')
553
- @hash = val
554
- @hash.default = default
555
- self
556
- end
557
-
558
- protected
559
- def _internal_hash #:nodoc:
560
- @hash
561
- end
562
-
563
- def update_container(key) #:nodoc:
564
- container = @hash[key]
565
- container = container.dup if container.equal?(default)
566
- container = yield(container)
567
- @hash[key] = container
568
- end
569
- end
@@ -1,185 +0,0 @@
1
- require 'set'
2
-
3
- # Multiset implements a collection of unordered values and
4
- # allows duplicates.
5
- #
6
- # == Example
7
- #
8
- # require 'multiset'
9
- # s1 = Multiset.new [1, 2] # -> #<Multiset: {1, 2}>
10
- # s1.add(2) # -> #<Multiset: {1, 2, 2}>
11
- # s1.merge([2, 6]) # -> #<Multiset: {1, 2, 2, 2, 3}>
12
- # s1.multiplicity(2) # -> 3
13
- # s1.multiplicity(3) # -> 1
14
- class Multiset < Set
15
- def initialize(*args, &block) #:nodoc:
16
- @hash = Hash.new(0)
17
- super
18
- end
19
-
20
- # Returns the number of times an element belongs to the multiset.
21
- def multiplicity(e)
22
- @hash[e]
23
- end
24
-
25
- # Returns the total number of elements in a multiset, including
26
- # repeated memberships
27
- def cardinality
28
- @hash.inject(0) { |s, (e, m)| s += m }
29
- end
30
- alias_method :size, :cardinality
31
- alias_method :length, :cardinality
32
-
33
- # Converts the set to an array. The order of elements is uncertain.
34
- def to_a
35
- inject([]) { |ary, (key, _)| ary << key }
36
- end
37
-
38
- # Returns true if the set is a superset of the given set.
39
- def superset?(set)
40
- set.is_a?(self.class) or raise ArgumentError, "value must be a set"
41
- return false if cardinality < set.cardinality
42
- set.all? { |o| set.multiplicity(o) <= multiplicity(o) }
43
- end
44
-
45
- # Returns true if the set is a proper superset of the given set.
46
- def proper_superset?(set)
47
- set.is_a?(self.class) or raise ArgumentError, "value must be a set"
48
- return false if cardinality <= set.cardinality
49
- set.all? { |o| set.multiplicity(o) <= multiplicity(o) }
50
- end
51
-
52
- # Returns true if the set is a subset of the given set.
53
- def subset?(set)
54
- set.is_a?(self.class) or raise ArgumentError, "value must be a set"
55
- return false if set.cardinality < cardinality
56
- all? { |o| multiplicity(o) <= set.multiplicity(o) }
57
- end
58
-
59
- # Returns true if the set is a proper subset of the given set.
60
- def proper_subset?(set)
61
- set.is_a?(self.class) or raise ArgumentError, "value must be a set"
62
- return false if set.cardinality <= cardinality
63
- all? { |o| multiplicity(o) <= set.multiplicity(o) }
64
- end
65
-
66
- # Calls the given block once for each element in the set, passing
67
- # the element as parameter. Returns an enumerator if no block is
68
- # given.
69
- def each
70
- @hash.each_pair do |key, multiplicity|
71
- multiplicity.times do
72
- yield(key)
73
- end
74
- end
75
- self
76
- end
77
-
78
- # Adds the given object to the set and returns self. Use +merge+ to
79
- # add many elements at once.
80
- def add(o)
81
- @hash[o] ||= 0
82
- @hash[o] += 1
83
- self
84
- end
85
- alias << add
86
-
87
- undef :add?
88
-
89
- # Deletes all the identical object from the set and returns self.
90
- # If +n+ is given, it will remove that amount of identical objects
91
- # from the set. Use +subtract+ to delete many different items at
92
- # once.
93
- def delete(o, n = nil)
94
- if n
95
- @hash[o] ||= 0
96
- @hash[o] -= n if @hash[o] > 0
97
- @hash.delete(o) if @hash[o] == 0
98
- else
99
- @hash.delete(o)
100
- end
101
- self
102
- end
103
-
104
- undef :delete?
105
-
106
- # Deletes every element of the set for which block evaluates to
107
- # true, and returns self.
108
- def delete_if
109
- each { |o| delete(o) if yield(o) }
110
- self
111
- end
112
-
113
- # Merges the elements of the given enumerable object to the set and
114
- # returns self.
115
- def merge(enum)
116
- enum.each { |o| add(o) }
117
- self
118
- end
119
-
120
- # Deletes every element that appears in the given enumerable object
121
- # and returns self.
122
- def subtract(enum)
123
- enum.each { |o| delete(o, 1) }
124
- self
125
- end
126
-
127
- # Returns a new set containing elements common to the set and the
128
- # given enumerable object.
129
- def &(enum)
130
- s = dup
131
- n = self.class.new
132
- enum.each { |o|
133
- if s.include?(o)
134
- s.delete(o, 1)
135
- n.add(o)
136
- end
137
- }
138
- n
139
- end
140
- alias intersection &
141
-
142
- # Returns a new set containing elements exclusive between the set
143
- # and the given enumerable object. (set ^ enum) is equivalent to
144
- # ((set | enum) - (set & enum)).
145
- def ^(enum)
146
- n = self.class.new(enum)
147
- each { |o| n.include?(o) ? n.delete(o, 1) : n.add(o) }
148
- n
149
- end
150
-
151
- # Returns true if two sets are equal. Two multisets are equal if
152
- # they have the same cardinalities and each element has the same
153
- # multiplicity in both sets. The equality of each element inside
154
- # the multiset is defined according to Object#eql?.
155
- def eql?(set)
156
- return true if equal?(set)
157
- set = self.class.new(set) unless set.is_a?(self.class)
158
- return false unless cardinality == set.cardinality
159
- superset?(set) && subset?(set)
160
- end
161
- alias_method :==, :eql?
162
-
163
- def marshal_dump #:nodoc:
164
- @hash
165
- end
166
-
167
- def marshal_load(hash) #:nodoc:
168
- @hash = hash
169
- end
170
-
171
- def to_yaml(opts = {}) #:nodoc:
172
- YAML::quick_emit(self, opts) do |out|
173
- out.map(taguri, to_yaml_style) do |map|
174
- @hash.each do |k, v|
175
- map.add(k, v)
176
- end
177
- end
178
- end
179
- end
180
-
181
- def yaml_initialize(tag, val) #:nodoc:
182
- @hash = val
183
- self
184
- end
185
- end
@@ -1,158 +0,0 @@
1
- require 'multimap'
2
-
3
- # NestedMultimap allows values to be assoicated with a nested
4
- # set of keys.
5
- class NestedMultimap < Multimap
6
- # call-seq:
7
- # multimap[*keys] = value => value
8
- # multimap.store(*keys, value) => value
9
- #
10
- # Associates the value given by <i>value</i> with multiple key
11
- # given by <i>keys</i>.
12
- #
13
- # map = NestedMultimap.new
14
- # map["a"] = 100
15
- # map["a", "b"] = 101
16
- # map["a"] = 102
17
- # map #=> {"a"=>{"b"=>[100, 101, 102], default => [100, 102]}}
18
- def store(*args)
19
- keys = args
20
- value = args.pop
21
-
22
- raise ArgumentError, 'wrong number of arguments (1 for 2)' unless value
23
-
24
- if keys.length > 1
25
- update_container(keys.shift) do |container|
26
- container = self.class.new(container) unless container.is_a?(self.class)
27
- container[*keys] = value
28
- container
29
- end
30
- elsif keys.length == 1
31
- super(keys.first, value)
32
- else
33
- self << value
34
- end
35
- end
36
- alias_method :[]=, :store
37
-
38
- # call-seq:
39
- # multimap << obj => multimap
40
- #
41
- # Pushes the given object on to the end of all the containers.
42
- #
43
- # map = NestedMultimap["a" => [100], "b" => [200, 300]]
44
- # map << 300
45
- # map["a"] #=> [100, 300]
46
- # map["c"] #=> [300]
47
- def <<(value)
48
- @hash.each_value { |container| container << value }
49
- self.default << value
50
- self
51
- end
52
-
53
- # call-seq:
54
- # multimap[*keys] => value
55
- # multimap[key1, key2, key3] => value
56
- #
57
- # Retrieves the <i>value</i> object corresponding to the
58
- # <i>*keys</i> object.
59
- def [](*keys)
60
- i, l, r, k = 0, keys.length, self, self.class
61
- while r.is_a?(k)
62
- r = i < l ? r._internal_hash[keys[i]] : r.default
63
- i += 1
64
- end
65
- r
66
- end
67
-
68
- # call-seq:
69
- # multimap.each_association { |key, container| block } => multimap
70
- #
71
- # Calls <i>block</i> once for each key/container in <i>map</i>, passing
72
- # the key and container to the block as parameters.
73
- #
74
- # map = NestedMultimap.new
75
- # map["a"] = 100
76
- # map["a", "b"] = 101
77
- # map["a"] = 102
78
- # map["c"] = 200
79
- # map.each_association { |key, container| puts "#{key} is #{container}" }
80
- #
81
- # <em>produces:</em>
82
- #
83
- # ["a", "b"] is [100, 101, 102]
84
- # "c" is [200]
85
- def each_association
86
- super() do |key, container|
87
- if container.respond_to?(:each_association)
88
- container.each_association do |nested_key, value|
89
- yield [key, nested_key].flatten, value
90
- end
91
- else
92
- yield key, container
93
- end
94
- end
95
- end
96
-
97
- # call-seq:
98
- # multimap.each_container_with_default { |container| block } => map
99
- #
100
- # Calls <i>block</i> for every container in <i>map</i> including
101
- # the default, passing the container as a parameter.
102
- #
103
- # map = NestedMultimap.new
104
- # map["a"] = 100
105
- # map["a", "b"] = 101
106
- # map["a"] = 102
107
- # map.each_container_with_default { |container| puts container }
108
- #
109
- # <em>produces:</em>
110
- #
111
- # [100, 101, 102]
112
- # [100, 102]
113
- # []
114
- def each_container_with_default(&block)
115
- @hash.each_value do |container|
116
- iterate_over_container(container, &block)
117
- end
118
- iterate_over_container(default, &block)
119
- self
120
- end
121
-
122
- # call-seq:
123
- # multimap.containers_with_default => array
124
- #
125
- # Returns a new array populated with all the containers from
126
- # <i>map</i> including the default.
127
- #
128
- # map = NestedMultimap.new
129
- # map["a"] = 100
130
- # map["a", "b"] = 101
131
- # map["a"] = 102
132
- # map.containers_with_default #=> [[100, 101, 102], [100, 102], []]
133
- def containers_with_default
134
- containers = []
135
- each_container_with_default { |container| containers << container }
136
- containers
137
- end
138
-
139
- def inspect #:nodoc:
140
- super.gsub(/\}$/, ", default => #{default.inspect}}")
141
- end
142
-
143
- private
144
- def iterate_over_container(container)
145
- if container.respond_to?(:each_container_with_default)
146
- container.each_container_with_default do |value|
147
- yield value
148
- end
149
- else
150
- yield container
151
- end
152
- end
153
- end
154
-
155
- begin
156
- require 'nested_multimap_ext'
157
- rescue LoadError
158
- end