rubymail 0.11
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.
- checksums.yaml +7 -0
- data/.gitignore +64 -0
- data/Gemfile +7 -0
- data/LICENSE +201 -0
- data/README.md +2 -0
- data/Rakefile +1 -0
- data/lib/multimap/.gitignore +4 -0
- data/lib/multimap/LICENSE +20 -0
- data/lib/multimap/README.rdoc +16 -0
- data/lib/multimap/Rakefile +34 -0
- data/lib/multimap/benchmarks/bm_nested_multimap_construction.rb +60 -0
- data/lib/multimap/benchmarks/bm_nested_multimap_lookup.rb +33 -0
- data/lib/multimap/ext/extconf.rb +6 -0
- data/lib/multimap/ext/nested_multimap_ext.c +24 -0
- data/lib/multimap/extras/graphing.rb +83 -0
- data/lib/multimap/lib/multimap.rb +569 -0
- data/lib/multimap/lib/multiset.rb +185 -0
- data/lib/multimap/lib/nested_multimap.rb +158 -0
- data/lib/multimap/spec/enumerable_examples.rb +50 -0
- data/lib/multimap/spec/hash_examples.rb +264 -0
- data/lib/multimap/spec/multimap_spec.rb +45 -0
- data/lib/multimap/spec/multiset_spec.rb +184 -0
- data/lib/multimap/spec/nested_multimap_spec.rb +202 -0
- data/lib/multimap/spec/set_examples.rb +301 -0
- data/lib/multimap/spec/spec_helper.rb +67 -0
- data/lib/rubymail/address.rb +17 -0
- data/lib/rubymail/base.rb +118 -0
- data/lib/rubymail/bounce.rb +31 -0
- data/lib/rubymail/client.rb +87 -0
- data/lib/rubymail/complaint.rb +31 -0
- data/lib/rubymail/domain.rb +34 -0
- data/lib/rubymail/list.rb +37 -0
- data/lib/rubymail/log.rb +19 -0
- data/lib/rubymail/mailbox.rb +41 -0
- data/lib/rubymail/message.rb +16 -0
- data/lib/rubymail/route.rb +99 -0
- data/lib/rubymail/rubymail_error.rb +53 -0
- data/lib/rubymail/secure.rb +19 -0
- data/lib/rubymail/unsubscribe.rb +31 -0
- data/lib/rubymail/webhook.rb +43 -0
- data/lib/rubymail.rb +31 -0
- data/rubymail.gemspec +18 -0
- data/spec/address_spec.rb +27 -0
- data/spec/base_spec.rb +132 -0
- data/spec/bounce_spec.rb +66 -0
- data/spec/client_spec.rb +118 -0
- data/spec/complaint_spec.rb +103 -0
- data/spec/domain_spec.rb +80 -0
- data/spec/helpers/rubymail_helper.rb +9 -0
- data/spec/list/member_spec.rb +82 -0
- data/spec/list/message_spec.rb +40 -0
- data/spec/list_spec.rb +70 -0
- data/spec/log_spec.rb +27 -0
- data/spec/mailbox_spec.rb +63 -0
- data/spec/route_spec.rb +100 -0
- data/spec/secure_spec.rb +54 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/unsubscribe_spec.rb +82 -0
- data/spec/webhook_spec.rb +115 -0
- metadata +159 -0
@@ -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
|
+
expect(@map.all? { |key, value| key =~ /\w/ }).to be_true
|
4
|
+
expect(@map.all? { |key, value| key =~ /\d/ }).to be_false
|
5
|
+
expect(@map.all? { |key, value| value > 0 }).to be_true
|
6
|
+
expect(@map.all? { |key, value| value > 200 }).to be_false
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should check any key/value pairs for condition" do
|
10
|
+
expect(@map.any? { |key, value| key == "a" }).to be_true
|
11
|
+
expect(@map.any? { |key, value| key == "z" }).to be_false
|
12
|
+
expect(@map.any? { |key, value| value == 100 }).to be_true
|
13
|
+
expect(@map.any? { |key, value| value > 1000 }).to be_false
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should collect key/value pairs" do
|
17
|
+
expect(@map.collect { |key, value| [key, value] }).to sorted_eql([["a", 100], ["b", 200], ["b", 300]])
|
18
|
+
expect(@map.map { |key, value| [key, value] }).to sorted_eql([["a", 100], ["b", 200], ["b", 300]])
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should detect key/value pair" do
|
22
|
+
expect(@map.detect { |key, value| value > 200 }).to eql(["b", 300])
|
23
|
+
expect(@map.find { |key, value| value > 200 }).to eql(["b", 300])
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should return entries" do
|
27
|
+
expect(@map.entries).to sorted_eql([["a", 100], ["b", 200], ["b", 300]])
|
28
|
+
expect(@map.to_a).to sorted_eql([["a", 100], ["b", 200], ["b", 300]])
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should find all key/value pairs" do
|
32
|
+
expect(@map.find_all { |key, value| value >= 200 }).to eql([["b", 200], ["b", 300]])
|
33
|
+
expect(@map.select { |key, value| value >= 200 }).to eql(Multimap["b", [200, 300]])
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should combine key/value pairs with inject" do
|
37
|
+
expect(@map.inject(0) { |sum, (key, value)| sum + value }).to eql(600)
|
38
|
+
|
39
|
+
@map.inject(0) { |memo, (key, value)|
|
40
|
+
memo > value ? memo : value
|
41
|
+
expect(}).to eql(300)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should check for key membership" do
|
45
|
+
expect(@map.member?("a")).to be_true
|
46
|
+
expect(@map.include?("a")).to be_true
|
47
|
+
expect(@map.member?("z")).to be_false
|
48
|
+
expect(@map.include?("z")).to 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
|
+
expect(@map).to eql(map2)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should not be equal to another Multimap if they contain different values" do
|
15
|
+
expect(@map).to_not == Multimap["a" => [100], "b" => [200]]
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should retrieve container of values for key" do
|
19
|
+
expect(@map["a"]).to eql(@container.new([100]))
|
20
|
+
expect(@map["b"]).to eql(@container.new([200, 300]))
|
21
|
+
expect(@map["z"]).to 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
|
+
expect(@map["a"]).to eql(@container.new([100, 400]))
|
28
|
+
expect(@map["b"]).to eql(@container.new([200, 300, 500]))
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should clear all key/values" do
|
32
|
+
@map.clear
|
33
|
+
expect(@map).to be_empty
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should be the class of the container" do
|
37
|
+
expect(@map.default.class).to eql(@container)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should delete all values at key" do
|
41
|
+
@map.delete("a")
|
42
|
+
expect(@map["a"]).to eql(@container.new)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should delete single value at key" do
|
46
|
+
@map.delete("b", 200)
|
47
|
+
expect(@map["b"]).to eql(@container.new([300]))
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should delete if key condition is matched" do
|
51
|
+
expect(@map.delete_if { |key, value| key >= "b" }).to eql(@map)
|
52
|
+
expect(@map["a"]).to eql(@container.new([100]))
|
53
|
+
expect(@map["b"]).to eql(@container.new)
|
54
|
+
|
55
|
+
expect(@map.delete_if { |key, value| key > "z" }).to eql(@map)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should delete if value condition is matched" do
|
59
|
+
expect(@map.delete_if { |key, value| value >= 300 }).to eql(@map)
|
60
|
+
expect(@map["a"]).to eql(@container.new([100]))
|
61
|
+
expect(@map["b"]).to eql(@container.new([200]))
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should duplicate the containers" do
|
65
|
+
map2 = @map.dup
|
66
|
+
expect(map2).to_not equal(@map)
|
67
|
+
expect(map2).to eql(@map)
|
68
|
+
expect(map2["a"]).to_not equal(@map["a"])
|
69
|
+
expect(map2["b"]).to_not equal(@map["b"])
|
70
|
+
expect(map2.default).to_not equal(@map.default)
|
71
|
+
expect(map2.default).to eql(@map.default)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should freeze containers" do
|
75
|
+
@map.freeze
|
76
|
+
expect(@map).to be_frozen
|
77
|
+
expect(@map["a"]).to be_frozen
|
78
|
+
expect(@map["b"]).to 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
|
+
expect(a).to 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
|
+
expect(a).to 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
|
+
expect(a).to 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
|
+
expect(a).to 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
|
+
expect(h).to 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
|
+
expect(a).to 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
|
+
expect(@map).to be_empty
|
120
|
+
end
|
121
|
+
|
122
|
+
it "should not be empty if there are any key/value pairs" do
|
123
|
+
expect(@map).to_not be_empty
|
124
|
+
end
|
125
|
+
|
126
|
+
it "should fetch container of values for key" do
|
127
|
+
expect(@map.fetch("a")).to eql(@container.new([100]))
|
128
|
+
expect(@map.fetch("b")).to eql(@container.new([200, 300]))
|
129
|
+
expect(lambda { @map.fetch("z") }).to raise_error(IndexError)
|
130
|
+
end
|
131
|
+
|
132
|
+
it "should check if key is present" do
|
133
|
+
expect(@map.has_key?("a")).to be_true
|
134
|
+
expect(@map.key?("a")).to be_true
|
135
|
+
expect(@map.has_key?("z")).to be_false
|
136
|
+
expect(@map.key?("z")).to be_false
|
137
|
+
end
|
138
|
+
|
139
|
+
it "should check containers when looking up by value" do
|
140
|
+
expect(@map.has_value?(100)).to be_true
|
141
|
+
expect(@map.value?(100)).to be_true
|
142
|
+
expect(@map.has_value?(999)).to be_false
|
143
|
+
expect(@map.value?(999)).to be_false
|
144
|
+
end
|
145
|
+
|
146
|
+
it "it should return the index for value" do
|
147
|
+
if @map.respond_to?(:index)
|
148
|
+
expect(@map.index(200)).to eql(@container.new(["b"]))
|
149
|
+
expect(@map.index(999)).to 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
|
+
expect(@map["a"]).to eql(@container.new)
|
156
|
+
expect(@map["c"]).to 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
|
+
expect(@map.invert).to eql(map2)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
it "should return array of keys" do
|
170
|
+
expect(@map.keys).to eql(["a", "b", "b"])
|
171
|
+
end
|
172
|
+
|
173
|
+
it "should return the number of key/value pairs" do
|
174
|
+
expect(@map.length).to eql(3)
|
175
|
+
expect(@map.size).to 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
|
+
expect(map["a"]).to eql(@container.new([100]))
|
181
|
+
expect(map["b"]).to eql(@container.new([200, 300, 254]))
|
182
|
+
expect(map["c"]).to eql(@container.new([300]))
|
183
|
+
|
184
|
+
expect(@map["a"]).to eql(@container.new([100]))
|
185
|
+
expect(@map["b"]).to eql(@container.new([200, 300]))
|
186
|
+
expect(@map["c"]).to eql(@container.new)
|
187
|
+
end
|
188
|
+
|
189
|
+
it "should update map" do
|
190
|
+
@map.update("b" => 254, "c" => @container.new([300]))
|
191
|
+
expect(@map["a"]).to eql(@container.new([100]))
|
192
|
+
expect(@map["b"]).to eql(@container.new([200, 300, 254]))
|
193
|
+
expect(@map["c"]).to eql(@container.new([300]))
|
194
|
+
|
195
|
+
klass = @map.class
|
196
|
+
@map.update(klass[@container.new, {"a" => @container.new([400, 500]), "c" => 600}])
|
197
|
+
expect(@map["a"]).to eql(@container.new([100, 400, 500]))
|
198
|
+
expect(@map["b"]).to eql(@container.new([200, 300, 254]))
|
199
|
+
expect(@map["c"]).to 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
|
+
expect(map["b"]).to eql(@container.new)
|
205
|
+
expect(@map["b"]).to 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
|
+
expect(map["b"]).to eql(@container.new([200]))
|
211
|
+
expect(@map["b"]).to eql(@container.new([200, 300]))
|
212
|
+
end
|
213
|
+
|
214
|
+
it "should reject key pairs" do
|
215
|
+
expect(@map.reject! { |key, value| key >= "b" }).to eql(@map)
|
216
|
+
expect(@map["a"]).to eql(@container.new([100]))
|
217
|
+
expect(@map["b"]).to eql(@container.new)
|
218
|
+
|
219
|
+
expect(@map.reject! { |key, value| key >= "z" }).to eql(nil)
|
220
|
+
end
|
221
|
+
|
222
|
+
it "should reject value pairs" do
|
223
|
+
expect(@map.reject! { |key, value| value >= 300 }).to eql(@map)
|
224
|
+
expect(@map["a"]).to eql(@container.new([100]))
|
225
|
+
expect(@map["b"]).to eql(@container.new([200]))
|
226
|
+
|
227
|
+
expect(@map.reject! { |key, value| key >= "z" }).to eql(nil)
|
228
|
+
end
|
229
|
+
|
230
|
+
it "should select key/value pairs" do
|
231
|
+
expect(@map.select { |k, v| k > "a" }).to eql(Multimap["b", [200, 300]])
|
232
|
+
expect(@map.select { |k, v| v < 200 }).to eql(Multimap["a", 100])
|
233
|
+
end
|
234
|
+
|
235
|
+
it "should convert to hash" do
|
236
|
+
expect(@map.to_hash["a"]).to eql(@container.new([100]))
|
237
|
+
expect(@map.to_hash["b"]).to eql(@container.new([200, 300]))
|
238
|
+
expect(@map.to_hash).to_not equal(@map)
|
239
|
+
end
|
240
|
+
|
241
|
+
it "should return all containers" do
|
242
|
+
expect(@map.containers).to sorted_eql([@container.new([100]), @container.new([200, 300])])
|
243
|
+
end
|
244
|
+
|
245
|
+
it "should return all values" do
|
246
|
+
expect(@map.values).to sorted_eql([100, 200, 300])
|
247
|
+
end
|
248
|
+
|
249
|
+
it "should return return values at keys" do
|
250
|
+
expect(@map.values_at("a", "b")).to eql([@container.new([100]), @container.new([200, 300])])
|
251
|
+
end
|
252
|
+
|
253
|
+
it "should marshal hash" do
|
254
|
+
data = Marshal.dump(@map)
|
255
|
+
expect(Marshal.load(data)).to eql(@map)
|
256
|
+
end
|
257
|
+
|
258
|
+
it "should dump yaml" do
|
259
|
+
require 'yaml'
|
260
|
+
|
261
|
+
data = YAML.dump(@map)
|
262
|
+
expect(YAML.load(data)).to eql(@map)
|
263
|
+
end
|
264
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Multimap, "with inital values {'a' => [100], 'b' => [200, 300]}" do
|
4
|
+
it_should_behave_like "Enumerable Multimap with inital values {'a' => [100], 'b' => [200, 300]}"
|
5
|
+
it_should_behave_like "Hash Multimap with inital values {'a' => [100], 'b' => [200, 300]}"
|
6
|
+
|
7
|
+
before do
|
8
|
+
@map = Multimap["a" => 100, "b" => [200, 300]]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe Multimap, "with inital values {'a' => [100], 'b' => [200, 300]}" do
|
13
|
+
it_should_behave_like "Enumerable Multimap with inital values {'a' => [100], 'b' => [200, 300]}"
|
14
|
+
it_should_behave_like "Hash Multimap with inital values {'a' => [100], 'b' => [200, 300]}"
|
15
|
+
|
16
|
+
before do
|
17
|
+
@map = Multimap["a", 100, "b", [200, 300]]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe Multimap, "with", Set do
|
22
|
+
it_should_behave_like "Enumerable Multimap with inital values {'a' => [100], 'b' => [200, 300]}"
|
23
|
+
it_should_behave_like "Hash Multimap with inital values {'a' => [100], 'b' => [200, 300]}"
|
24
|
+
|
25
|
+
before do
|
26
|
+
@container = Set
|
27
|
+
@map = Multimap.new(@container.new)
|
28
|
+
@map["a"] = 100
|
29
|
+
@map["b"] = 200
|
30
|
+
@map["b"] = 300
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe Multimap, "with", MiniArray do
|
35
|
+
it_should_behave_like "Enumerable Multimap with inital values {'a' => [100], 'b' => [200, 300]}"
|
36
|
+
it_should_behave_like "Hash Multimap with inital values {'a' => [100], 'b' => [200, 300]}"
|
37
|
+
|
38
|
+
before do
|
39
|
+
@container = MiniArray
|
40
|
+
@map = Multimap.new(@container.new)
|
41
|
+
@map["a"] = 100
|
42
|
+
@map["b"] = 200
|
43
|
+
@map["b"] = 300
|
44
|
+
end
|
45
|
+
end
|