mailgun 0.7 → 0.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,185 @@
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
@@ -0,0 +1,158 @@
1
+ require 'multimap/lib/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
@@ -0,0 +1,50 @@
1
+ shared_examples_for Enumerable, Multimap, "with inital values {'a' => [100], 'b' => [200, 300]}" do
2
+ it "should check all key/value pairs for condition" do
3
+ @map.all? { |key, value| key =~ /\w/ }.should be_true
4
+ @map.all? { |key, value| key =~ /\d/ }.should be_false
5
+ @map.all? { |key, value| value > 0 }.should be_true
6
+ @map.all? { |key, value| value > 200 }.should be_false
7
+ end
8
+
9
+ it "should check any key/value pairs for condition" do
10
+ @map.any? { |key, value| key == "a" }.should be_true
11
+ @map.any? { |key, value| key == "z" }.should be_false
12
+ @map.any? { |key, value| value == 100 }.should be_true
13
+ @map.any? { |key, value| value > 1000 }.should be_false
14
+ end
15
+
16
+ it "should collect key/value pairs" do
17
+ @map.collect { |key, value| [key, value] }.should sorted_eql([["a", 100], ["b", 200], ["b", 300]])
18
+ @map.map { |key, value| [key, value] }.should sorted_eql([["a", 100], ["b", 200], ["b", 300]])
19
+ end
20
+
21
+ it "should detect key/value pair" do
22
+ @map.detect { |key, value| value > 200 }.should eql(["b", 300])
23
+ @map.find { |key, value| value > 200 }.should eql(["b", 300])
24
+ end
25
+
26
+ it "should return entries" do
27
+ @map.entries.should sorted_eql([["a", 100], ["b", 200], ["b", 300]])
28
+ @map.to_a.should sorted_eql([["a", 100], ["b", 200], ["b", 300]])
29
+ end
30
+
31
+ it "should find all key/value pairs" do
32
+ @map.find_all { |key, value| value >= 200 }.should eql([["b", 200], ["b", 300]])
33
+ @map.select { |key, value| value >= 200 }.should eql(Multimap["b", [200, 300]])
34
+ end
35
+
36
+ it "should combine key/value pairs with inject" do
37
+ @map.inject(0) { |sum, (key, value)| sum + value }.should eql(600)
38
+
39
+ @map.inject(0) { |memo, (key, value)|
40
+ memo > value ? memo : value
41
+ }.should eql(300)
42
+ end
43
+
44
+ it "should check for key membership" do
45
+ @map.member?("a").should be_true
46
+ @map.include?("a").should be_true
47
+ @map.member?("z").should be_false
48
+ @map.include?("z").should be_false
49
+ end
50
+ end
@@ -0,0 +1,264 @@
1
+ shared_examples_for Hash, Multimap, "with inital values {'a' => [100], 'b' => [200, 300]}" do
2
+ before do
3
+ @container ||= Array
4
+ end
5
+
6
+ it "should be equal to another Multimap if they contain the same keys and values" do
7
+ map2 = Multimap.new(@container.new)
8
+ map2["a"] = 100
9
+ map2["b"] = 200
10
+ map2["b"] = 300
11
+ @map.should eql(map2)
12
+ end
13
+
14
+ it "should not be equal to another Multimap if they contain different values" do
15
+ @map.should_not == Multimap["a" => [100], "b" => [200]]
16
+ end
17
+
18
+ it "should retrieve container of values for key" do
19
+ @map["a"].should eql(@container.new([100]))
20
+ @map["b"].should eql(@container.new([200, 300]))
21
+ @map["z"].should eql(@container.new)
22
+ end
23
+
24
+ it "should append values to container at key" do
25
+ @map["a"] = 400
26
+ @map.store("b", 500)
27
+ @map["a"].should eql(@container.new([100, 400]))
28
+ @map["b"].should eql(@container.new([200, 300, 500]))
29
+ end
30
+
31
+ it "should clear all key/values" do
32
+ @map.clear
33
+ @map.should be_empty
34
+ end
35
+
36
+ it "should be the class of the container" do
37
+ @map.default.class.should eql(@container)
38
+ end
39
+
40
+ it "should delete all values at key" do
41
+ @map.delete("a")
42
+ @map["a"].should eql(@container.new)
43
+ end
44
+
45
+ it "should delete single value at key" do
46
+ @map.delete("b", 200)
47
+ @map["b"].should eql(@container.new([300]))
48
+ end
49
+
50
+ it "should delete if key condition is matched" do
51
+ @map.delete_if { |key, value| key >= "b" }.should eql(@map)
52
+ @map["a"].should eql(@container.new([100]))
53
+ @map["b"].should eql(@container.new)
54
+
55
+ @map.delete_if { |key, value| key > "z" }.should eql(@map)
56
+ end
57
+
58
+ it "should delete if value condition is matched" do
59
+ @map.delete_if { |key, value| value >= 300 }.should eql(@map)
60
+ @map["a"].should eql(@container.new([100]))
61
+ @map["b"].should eql(@container.new([200]))
62
+ end
63
+
64
+ it "should duplicate the containers" do
65
+ map2 = @map.dup
66
+ map2.should_not equal(@map)
67
+ map2.should eql(@map)
68
+ map2["a"].should_not equal(@map["a"])
69
+ map2["b"].should_not equal(@map["b"])
70
+ map2.default.should_not equal(@map.default)
71
+ map2.default.should eql(@map.default)
72
+ end
73
+
74
+ it "should freeze containers" do
75
+ @map.freeze
76
+ @map.should be_frozen
77
+ @map["a"].should be_frozen
78
+ @map["b"].should be_frozen
79
+ end
80
+
81
+ it "should iterate over each key/value pair and yield an array" do
82
+ a = []
83
+ @map.each { |pair| a << pair }
84
+ a.should sorted_eql([["a", 100], ["b", 200], ["b", 300]])
85
+ end
86
+
87
+ it "should iterate over each container" do
88
+ a = []
89
+ @map.each_container { |container| a << container }
90
+ a.should sorted_eql([@container.new([100]), @container.new([200, 300])])
91
+ end
92
+
93
+ it "should iterate over each key/container" do
94
+ a = []
95
+ @map.each_association { |key, container| a << [key, container] }
96
+ a.should sorted_eql([["a", @container.new([100])], ["b", @container.new([200, 300])]])
97
+ end
98
+
99
+ it "should iterate over each key" do
100
+ a = []
101
+ @map.each_key { |key| a << key }
102
+ a.should sorted_eql(["a", "b", "b"])
103
+ end
104
+
105
+ it "should iterate over each key/value pair and yield the pair" do
106
+ h = {}
107
+ @map.each_pair { |key, value| (h[key] ||= []) << value }
108
+ h.should eql({ "a" => [100], "b" => [200, 300] })
109
+ end
110
+
111
+ it "should iterate over each value" do
112
+ a = []
113
+ @map.each_value { |value| a << value }
114
+ a.should sorted_eql([100, 200, 300])
115
+ end
116
+
117
+ it "should be empty if there are no key/value pairs" do
118
+ @map.clear
119
+ @map.should be_empty
120
+ end
121
+
122
+ it "should not be empty if there are any key/value pairs" do
123
+ @map.should_not be_empty
124
+ end
125
+
126
+ it "should fetch container of values for key" do
127
+ @map.fetch("a").should eql(@container.new([100]))
128
+ @map.fetch("b").should eql(@container.new([200, 300]))
129
+ lambda { @map.fetch("z") }.should raise_error(IndexError)
130
+ end
131
+
132
+ it "should check if key is present" do
133
+ @map.has_key?("a").should be_true
134
+ @map.key?("a").should be_true
135
+ @map.has_key?("z").should be_false
136
+ @map.key?("z").should be_false
137
+ end
138
+
139
+ it "should check containers when looking up by value" do
140
+ @map.has_value?(100).should be_true
141
+ @map.value?(100).should be_true
142
+ @map.has_value?(999).should be_false
143
+ @map.value?(999).should be_false
144
+ end
145
+
146
+ it "it should return the index for value" do
147
+ if @map.respond_to?(:index)
148
+ @map.index(200).should eql(@container.new(["b"]))
149
+ @map.index(999).should eql(@container.new)
150
+ end
151
+ end
152
+
153
+ it "should replace the contents of hash" do
154
+ @map.replace({ "c" => @container.new([300]), "d" => @container.new([400]) })
155
+ @map["a"].should eql(@container.new)
156
+ @map["c"].should eql(@container.new([300]))
157
+ end
158
+
159
+ it "should return an inverted Multimap" do
160
+ if @map.respond_to?(:invert)
161
+ map2 = Multimap.new(@container.new)
162
+ map2[100] = "a"
163
+ map2[200] = "b"
164
+ map2[300] = "b"
165
+ @map.invert.should eql(map2)
166
+ end
167
+ end
168
+
169
+ it "should return array of keys" do
170
+ @map.keys.should eql(["a", "b", "b"])
171
+ end
172
+
173
+ it "should return the number of key/value pairs" do
174
+ @map.length.should eql(3)
175
+ @map.size.should eql(3)
176
+ end
177
+
178
+ it "should duplicate map and with merged values" do
179
+ map = @map.merge("b" => 254, "c" => @container.new([300]))
180
+ map["a"].should eql(@container.new([100]))
181
+ map["b"].should eql(@container.new([200, 300, 254]))
182
+ map["c"].should eql(@container.new([300]))
183
+
184
+ @map["a"].should eql(@container.new([100]))
185
+ @map["b"].should eql(@container.new([200, 300]))
186
+ @map["c"].should eql(@container.new)
187
+ end
188
+
189
+ it "should update map" do
190
+ @map.update("b" => 254, "c" => @container.new([300]))
191
+ @map["a"].should eql(@container.new([100]))
192
+ @map["b"].should eql(@container.new([200, 300, 254]))
193
+ @map["c"].should eql(@container.new([300]))
194
+
195
+ klass = @map.class
196
+ @map.update(klass[@container.new, {"a" => @container.new([400, 500]), "c" => 600}])
197
+ @map["a"].should eql(@container.new([100, 400, 500]))
198
+ @map["b"].should eql(@container.new([200, 300, 254]))
199
+ @map["c"].should eql(@container.new([300, 600]))
200
+ end
201
+
202
+ it "should reject key pairs on copy of the map" do
203
+ map = @map.reject { |key, value| key >= "b" }
204
+ map["b"].should eql(@container.new)
205
+ @map["b"].should eql(@container.new([200, 300]))
206
+ end
207
+
208
+ it "should reject value pairs on copy of the map" do
209
+ map = @map.reject { |key, value| value >= 300 }
210
+ map["b"].should eql(@container.new([200]))
211
+ @map["b"].should eql(@container.new([200, 300]))
212
+ end
213
+
214
+ it "should reject key pairs" do
215
+ @map.reject! { |key, value| key >= "b" }.should eql(@map)
216
+ @map["a"].should eql(@container.new([100]))
217
+ @map["b"].should eql(@container.new)
218
+
219
+ @map.reject! { |key, value| key >= "z" }.should eql(nil)
220
+ end
221
+
222
+ it "should reject value pairs" do
223
+ @map.reject! { |key, value| value >= 300 }.should eql(@map)
224
+ @map["a"].should eql(@container.new([100]))
225
+ @map["b"].should eql(@container.new([200]))
226
+
227
+ @map.reject! { |key, value| key >= "z" }.should eql(nil)
228
+ end
229
+
230
+ it "should select key/value pairs" do
231
+ @map.select { |k, v| k > "a" }.should eql(Multimap["b", [200, 300]])
232
+ @map.select { |k, v| v < 200 }.should eql(Multimap["a", 100])
233
+ end
234
+
235
+ it "should convert to hash" do
236
+ @map.to_hash["a"].should eql(@container.new([100]))
237
+ @map.to_hash["b"].should eql(@container.new([200, 300]))
238
+ @map.to_hash.should_not equal(@map)
239
+ end
240
+
241
+ it "should return all containers" do
242
+ @map.containers.should sorted_eql([@container.new([100]), @container.new([200, 300])])
243
+ end
244
+
245
+ it "should return all values" do
246
+ @map.values.should sorted_eql([100, 200, 300])
247
+ end
248
+
249
+ it "should return return values at keys" do
250
+ @map.values_at("a", "b").should eql([@container.new([100]), @container.new([200, 300])])
251
+ end
252
+
253
+ it "should marshal hash" do
254
+ data = Marshal.dump(@map)
255
+ Marshal.load(data).should eql(@map)
256
+ end
257
+
258
+ it "should dump yaml" do
259
+ require 'yaml'
260
+
261
+ data = YAML.dump(@map)
262
+ YAML.load(data).should eql(@map)
263
+ end
264
+ end