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.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +64 -0
  3. data/Gemfile +7 -0
  4. data/LICENSE +201 -0
  5. data/README.md +2 -0
  6. data/Rakefile +1 -0
  7. data/lib/multimap/.gitignore +4 -0
  8. data/lib/multimap/LICENSE +20 -0
  9. data/lib/multimap/README.rdoc +16 -0
  10. data/lib/multimap/Rakefile +34 -0
  11. data/lib/multimap/benchmarks/bm_nested_multimap_construction.rb +60 -0
  12. data/lib/multimap/benchmarks/bm_nested_multimap_lookup.rb +33 -0
  13. data/lib/multimap/ext/extconf.rb +6 -0
  14. data/lib/multimap/ext/nested_multimap_ext.c +24 -0
  15. data/lib/multimap/extras/graphing.rb +83 -0
  16. data/lib/multimap/lib/multimap.rb +569 -0
  17. data/lib/multimap/lib/multiset.rb +185 -0
  18. data/lib/multimap/lib/nested_multimap.rb +158 -0
  19. data/lib/multimap/spec/enumerable_examples.rb +50 -0
  20. data/lib/multimap/spec/hash_examples.rb +264 -0
  21. data/lib/multimap/spec/multimap_spec.rb +45 -0
  22. data/lib/multimap/spec/multiset_spec.rb +184 -0
  23. data/lib/multimap/spec/nested_multimap_spec.rb +202 -0
  24. data/lib/multimap/spec/set_examples.rb +301 -0
  25. data/lib/multimap/spec/spec_helper.rb +67 -0
  26. data/lib/rubymail/address.rb +17 -0
  27. data/lib/rubymail/base.rb +118 -0
  28. data/lib/rubymail/bounce.rb +31 -0
  29. data/lib/rubymail/client.rb +87 -0
  30. data/lib/rubymail/complaint.rb +31 -0
  31. data/lib/rubymail/domain.rb +34 -0
  32. data/lib/rubymail/list.rb +37 -0
  33. data/lib/rubymail/log.rb +19 -0
  34. data/lib/rubymail/mailbox.rb +41 -0
  35. data/lib/rubymail/message.rb +16 -0
  36. data/lib/rubymail/route.rb +99 -0
  37. data/lib/rubymail/rubymail_error.rb +53 -0
  38. data/lib/rubymail/secure.rb +19 -0
  39. data/lib/rubymail/unsubscribe.rb +31 -0
  40. data/lib/rubymail/webhook.rb +43 -0
  41. data/lib/rubymail.rb +31 -0
  42. data/rubymail.gemspec +18 -0
  43. data/spec/address_spec.rb +27 -0
  44. data/spec/base_spec.rb +132 -0
  45. data/spec/bounce_spec.rb +66 -0
  46. data/spec/client_spec.rb +118 -0
  47. data/spec/complaint_spec.rb +103 -0
  48. data/spec/domain_spec.rb +80 -0
  49. data/spec/helpers/rubymail_helper.rb +9 -0
  50. data/spec/list/member_spec.rb +82 -0
  51. data/spec/list/message_spec.rb +40 -0
  52. data/spec/list_spec.rb +70 -0
  53. data/spec/log_spec.rb +27 -0
  54. data/spec/mailbox_spec.rb +63 -0
  55. data/spec/route_spec.rb +100 -0
  56. data/spec/secure_spec.rb +54 -0
  57. data/spec/spec_helper.rb +10 -0
  58. data/spec/unsubscribe_spec.rb +82 -0
  59. data/spec/webhook_spec.rb +115 -0
  60. metadata +159 -0
@@ -0,0 +1,569 @@
1
+ require 'forwardable'
2
+ require 'multimap/lib/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