mail-portertech 2.6.2.edge

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 (153) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.rdoc +753 -0
  3. data/CONTRIBUTING.md +60 -0
  4. data/Dependencies.txt +2 -0
  5. data/Gemfile +15 -0
  6. data/MIT-LICENSE +20 -0
  7. data/README.md +683 -0
  8. data/Rakefile +29 -0
  9. data/TODO.rdoc +9 -0
  10. data/lib/mail.rb +91 -0
  11. data/lib/mail/attachments_list.rb +104 -0
  12. data/lib/mail/body.rb +291 -0
  13. data/lib/mail/check_delivery_params.rb +20 -0
  14. data/lib/mail/configuration.rb +75 -0
  15. data/lib/mail/core_extensions/nil.rb +19 -0
  16. data/lib/mail/core_extensions/object.rb +13 -0
  17. data/lib/mail/core_extensions/smtp.rb +24 -0
  18. data/lib/mail/core_extensions/string.rb +43 -0
  19. data/lib/mail/core_extensions/string/access.rb +145 -0
  20. data/lib/mail/core_extensions/string/multibyte.rb +78 -0
  21. data/lib/mail/elements.rb +14 -0
  22. data/lib/mail/elements/address.rb +270 -0
  23. data/lib/mail/elements/address_list.rb +51 -0
  24. data/lib/mail/elements/content_disposition_element.rb +26 -0
  25. data/lib/mail/elements/content_location_element.rb +21 -0
  26. data/lib/mail/elements/content_transfer_encoding_element.rb +17 -0
  27. data/lib/mail/elements/content_type_element.rb +31 -0
  28. data/lib/mail/elements/date_time_element.rb +22 -0
  29. data/lib/mail/elements/envelope_from_element.rb +39 -0
  30. data/lib/mail/elements/message_ids_element.rb +24 -0
  31. data/lib/mail/elements/mime_version_element.rb +22 -0
  32. data/lib/mail/elements/phrase_list.rb +16 -0
  33. data/lib/mail/elements/received_element.rb +26 -0
  34. data/lib/mail/encodings.rb +304 -0
  35. data/lib/mail/encodings/7bit.rb +31 -0
  36. data/lib/mail/encodings/8bit.rb +31 -0
  37. data/lib/mail/encodings/base64.rb +33 -0
  38. data/lib/mail/encodings/binary.rb +31 -0
  39. data/lib/mail/encodings/quoted_printable.rb +39 -0
  40. data/lib/mail/encodings/transfer_encoding.rb +58 -0
  41. data/lib/mail/envelope.rb +30 -0
  42. data/lib/mail/field.rb +247 -0
  43. data/lib/mail/field_list.rb +33 -0
  44. data/lib/mail/fields.rb +35 -0
  45. data/lib/mail/fields/bcc_field.rb +56 -0
  46. data/lib/mail/fields/cc_field.rb +55 -0
  47. data/lib/mail/fields/comments_field.rb +41 -0
  48. data/lib/mail/fields/common/address_container.rb +16 -0
  49. data/lib/mail/fields/common/common_address.rb +135 -0
  50. data/lib/mail/fields/common/common_date.rb +35 -0
  51. data/lib/mail/fields/common/common_field.rb +57 -0
  52. data/lib/mail/fields/common/common_message_id.rb +48 -0
  53. data/lib/mail/fields/common/parameter_hash.rb +58 -0
  54. data/lib/mail/fields/content_description_field.rb +19 -0
  55. data/lib/mail/fields/content_disposition_field.rb +70 -0
  56. data/lib/mail/fields/content_id_field.rb +62 -0
  57. data/lib/mail/fields/content_location_field.rb +42 -0
  58. data/lib/mail/fields/content_transfer_encoding_field.rb +44 -0
  59. data/lib/mail/fields/content_type_field.rb +201 -0
  60. data/lib/mail/fields/date_field.rb +57 -0
  61. data/lib/mail/fields/from_field.rb +55 -0
  62. data/lib/mail/fields/in_reply_to_field.rb +56 -0
  63. data/lib/mail/fields/keywords_field.rb +44 -0
  64. data/lib/mail/fields/message_id_field.rb +82 -0
  65. data/lib/mail/fields/mime_version_field.rb +53 -0
  66. data/lib/mail/fields/optional_field.rb +13 -0
  67. data/lib/mail/fields/received_field.rb +75 -0
  68. data/lib/mail/fields/references_field.rb +56 -0
  69. data/lib/mail/fields/reply_to_field.rb +55 -0
  70. data/lib/mail/fields/resent_bcc_field.rb +55 -0
  71. data/lib/mail/fields/resent_cc_field.rb +55 -0
  72. data/lib/mail/fields/resent_date_field.rb +35 -0
  73. data/lib/mail/fields/resent_from_field.rb +55 -0
  74. data/lib/mail/fields/resent_message_id_field.rb +34 -0
  75. data/lib/mail/fields/resent_sender_field.rb +62 -0
  76. data/lib/mail/fields/resent_to_field.rb +55 -0
  77. data/lib/mail/fields/return_path_field.rb +65 -0
  78. data/lib/mail/fields/sender_field.rb +67 -0
  79. data/lib/mail/fields/structured_field.rb +51 -0
  80. data/lib/mail/fields/subject_field.rb +16 -0
  81. data/lib/mail/fields/to_field.rb +55 -0
  82. data/lib/mail/fields/unstructured_field.rb +204 -0
  83. data/lib/mail/header.rb +274 -0
  84. data/lib/mail/indifferent_hash.rb +146 -0
  85. data/lib/mail/mail.rb +267 -0
  86. data/lib/mail/matchers/has_sent_mail.rb +157 -0
  87. data/lib/mail/message.rb +2160 -0
  88. data/lib/mail/multibyte.rb +42 -0
  89. data/lib/mail/multibyte/chars.rb +474 -0
  90. data/lib/mail/multibyte/exceptions.rb +8 -0
  91. data/lib/mail/multibyte/unicode.rb +400 -0
  92. data/lib/mail/multibyte/utils.rb +60 -0
  93. data/lib/mail/network.rb +14 -0
  94. data/lib/mail/network/delivery_methods/exim.rb +52 -0
  95. data/lib/mail/network/delivery_methods/file_delivery.rb +45 -0
  96. data/lib/mail/network/delivery_methods/sendmail.rb +89 -0
  97. data/lib/mail/network/delivery_methods/smtp.rb +142 -0
  98. data/lib/mail/network/delivery_methods/smtp_connection.rb +61 -0
  99. data/lib/mail/network/delivery_methods/test_mailer.rb +44 -0
  100. data/lib/mail/network/retriever_methods/base.rb +63 -0
  101. data/lib/mail/network/retriever_methods/imap.rb +173 -0
  102. data/lib/mail/network/retriever_methods/pop3.rb +140 -0
  103. data/lib/mail/network/retriever_methods/test_retriever.rb +43 -0
  104. data/lib/mail/parsers.rb +26 -0
  105. data/lib/mail/parsers/address_lists_parser.rb +132 -0
  106. data/lib/mail/parsers/content_disposition_parser.rb +67 -0
  107. data/lib/mail/parsers/content_location_parser.rb +35 -0
  108. data/lib/mail/parsers/content_transfer_encoding_parser.rb +33 -0
  109. data/lib/mail/parsers/content_type_parser.rb +64 -0
  110. data/lib/mail/parsers/date_time_parser.rb +36 -0
  111. data/lib/mail/parsers/envelope_from_parser.rb +45 -0
  112. data/lib/mail/parsers/message_ids_parser.rb +39 -0
  113. data/lib/mail/parsers/mime_version_parser.rb +41 -0
  114. data/lib/mail/parsers/phrase_lists_parser.rb +33 -0
  115. data/lib/mail/parsers/ragel.rb +17 -0
  116. data/lib/mail/parsers/ragel/common.rl +184 -0
  117. data/lib/mail/parsers/ragel/date_time.rl +30 -0
  118. data/lib/mail/parsers/ragel/parser_info.rb +61 -0
  119. data/lib/mail/parsers/ragel/ruby.rb +39 -0
  120. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb +14864 -0
  121. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb.rl +37 -0
  122. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb +751 -0
  123. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb.rl +37 -0
  124. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb +614 -0
  125. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb.rl +37 -0
  126. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb +447 -0
  127. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb.rl +37 -0
  128. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb +825 -0
  129. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb.rl +37 -0
  130. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb +817 -0
  131. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb.rl +37 -0
  132. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb +2129 -0
  133. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb.rl +37 -0
  134. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb +1570 -0
  135. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb.rl +37 -0
  136. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb +440 -0
  137. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb.rl +37 -0
  138. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb +564 -0
  139. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb.rl +37 -0
  140. data/lib/mail/parsers/ragel/ruby/machines/rb_actions.rl +51 -0
  141. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb +5144 -0
  142. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb.rl +37 -0
  143. data/lib/mail/parsers/ragel/ruby/parser.rb.rl.erb +37 -0
  144. data/lib/mail/parsers/received_parser.rb +47 -0
  145. data/lib/mail/part.rb +120 -0
  146. data/lib/mail/parts_list.rb +57 -0
  147. data/lib/mail/patterns.rb +37 -0
  148. data/lib/mail/utilities.rb +225 -0
  149. data/lib/mail/values/unicode_tables.dat +0 -0
  150. data/lib/mail/version.rb +4 -0
  151. data/lib/mail/version_specific/ruby_1_8.rb +119 -0
  152. data/lib/mail/version_specific/ruby_1_9.rb +159 -0
  153. metadata +276 -0
@@ -0,0 +1,146 @@
1
+ # encoding: utf-8
2
+
3
+ # This is an almost cut and paste from ActiveSupport v3.0.6, copied in here so that Mail
4
+ # itself does not depend on ActiveSupport to avoid versioning conflicts
5
+
6
+ module Mail
7
+ class IndifferentHash < Hash
8
+
9
+ def initialize(constructor = {})
10
+ if constructor.is_a?(Hash)
11
+ super()
12
+ update(constructor)
13
+ else
14
+ super(constructor)
15
+ end
16
+ end
17
+
18
+ def default(key = nil)
19
+ if key.is_a?(Symbol) && include?(key = key.to_s)
20
+ self[key]
21
+ else
22
+ super
23
+ end
24
+ end
25
+
26
+ def self.new_from_hash_copying_default(hash)
27
+ IndifferentHash.new(hash).tap do |new_hash|
28
+ new_hash.default = hash.default
29
+ end
30
+ end
31
+
32
+ alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
33
+ alias_method :regular_update, :update unless method_defined?(:regular_update)
34
+
35
+ # Assigns a new value to the hash:
36
+ #
37
+ # hash = HashWithIndifferentAccess.new
38
+ # hash[:key] = "value"
39
+ #
40
+ def []=(key, value)
41
+ regular_writer(convert_key(key), convert_value(value))
42
+ end
43
+
44
+ alias_method :store, :[]=
45
+
46
+ # Updates the instantized hash with values from the second:
47
+ #
48
+ # hash_1 = HashWithIndifferentAccess.new
49
+ # hash_1[:key] = "value"
50
+ #
51
+ # hash_2 = HashWithIndifferentAccess.new
52
+ # hash_2[:key] = "New Value!"
53
+ #
54
+ # hash_1.update(hash_2) # => {"key"=>"New Value!"}
55
+ #
56
+ def update(other_hash)
57
+ other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
58
+ self
59
+ end
60
+
61
+ alias_method :merge!, :update
62
+
63
+ # Checks the hash for a key matching the argument passed in:
64
+ #
65
+ # hash = HashWithIndifferentAccess.new
66
+ # hash["key"] = "value"
67
+ # hash.key? :key # => true
68
+ # hash.key? "key" # => true
69
+ #
70
+ def key?(key)
71
+ super(convert_key(key))
72
+ end
73
+
74
+ alias_method :include?, :key?
75
+ alias_method :has_key?, :key?
76
+ alias_method :member?, :key?
77
+
78
+ # Fetches the value for the specified key, same as doing hash[key]
79
+ def fetch(key, *extras)
80
+ super(convert_key(key), *extras)
81
+ end
82
+
83
+ # Returns an array of the values at the specified indices:
84
+ #
85
+ # hash = HashWithIndifferentAccess.new
86
+ # hash[:a] = "x"
87
+ # hash[:b] = "y"
88
+ # hash.values_at("a", "b") # => ["x", "y"]
89
+ #
90
+ def values_at(*indices)
91
+ indices.collect {|key| self[convert_key(key)]}
92
+ end
93
+
94
+ # Returns an exact copy of the hash.
95
+ def dup
96
+ IndifferentHash.new(self)
97
+ end
98
+
99
+ # Merges the instantized and the specified hashes together, giving precedence to the values from the second hash
100
+ # Does not overwrite the existing hash.
101
+ def merge(hash)
102
+ self.dup.update(hash)
103
+ end
104
+
105
+ # Performs the opposite of merge, with the keys and values from the first hash taking precedence over the second.
106
+ # This overloaded definition prevents returning a regular hash, if reverse_merge is called on a HashWithDifferentAccess.
107
+ def reverse_merge(other_hash)
108
+ super self.class.new_from_hash_copying_default(other_hash)
109
+ end
110
+
111
+ def reverse_merge!(other_hash)
112
+ replace(reverse_merge( other_hash ))
113
+ end
114
+
115
+ # Removes a specified key from the hash.
116
+ def delete(key)
117
+ super(convert_key(key))
118
+ end
119
+
120
+ def stringify_keys!; self end
121
+ def stringify_keys; dup end
122
+ def symbolize_keys; to_hash.symbolize_keys end
123
+ def to_options!; self end
124
+
125
+ def to_hash
126
+ Hash.new(default).merge!(self)
127
+ end
128
+
129
+ protected
130
+
131
+ def convert_key(key)
132
+ key.kind_of?(Symbol) ? key.to_s : key
133
+ end
134
+
135
+ def convert_value(value)
136
+ if value.class == Hash
137
+ self.class.new_from_hash_copying_default(value)
138
+ elsif value.is_a?(Array)
139
+ value.dup.replace(value.map { |e| convert_value(e) })
140
+ else
141
+ value
142
+ end
143
+ end
144
+
145
+ end
146
+ end
@@ -0,0 +1,267 @@
1
+ # encoding: utf-8
2
+ module Mail
3
+
4
+ # Allows you to create a new Mail::Message object.
5
+ #
6
+ # You can make an email via passing a string or passing a block.
7
+ #
8
+ # For example, the following two examples will create the same email
9
+ # message:
10
+ #
11
+ # Creating via a string:
12
+ #
13
+ # string = "To: mikel@test.lindsaar.net\r\n"
14
+ # string << "From: bob@test.lindsaar.net\r\n"
15
+ # string << "Subject: This is an email\r\n"
16
+ # string << "\r\n"
17
+ # string << "This is the body"
18
+ # Mail.new(string)
19
+ #
20
+ # Or creating via a block:
21
+ #
22
+ # message = Mail.new do
23
+ # to 'mikel@test.lindsaar.net'
24
+ # from 'bob@test.lindsaar.net'
25
+ # subject 'This is an email'
26
+ # body 'This is the body'
27
+ # end
28
+ #
29
+ # Or creating via a hash (or hash like object):
30
+ #
31
+ # message = Mail.new({:to => 'mikel@test.lindsaar.net',
32
+ # 'from' => 'bob@test.lindsaar.net',
33
+ # :subject => 'This is an email',
34
+ # :body => 'This is the body' })
35
+ #
36
+ # Note, the hash keys can be strings or symbols, the passed in object
37
+ # does not need to be a hash, it just needs to respond to :each_pair
38
+ # and yield each key value pair.
39
+ #
40
+ # As a side note, you can also create a new email through creating
41
+ # a Mail::Message object directly and then passing in values via string,
42
+ # symbol or direct method calls. See Mail::Message for more information.
43
+ #
44
+ # mail = Mail.new
45
+ # mail.to = 'mikel@test.lindsaar.net'
46
+ # mail[:from] = 'bob@test.lindsaar.net'
47
+ # mail['subject'] = 'This is an email'
48
+ # mail.body = 'This is the body'
49
+ def self.new(*args, &block)
50
+ Message.new(args, &block)
51
+ end
52
+
53
+ # Sets the default delivery method and retriever method for all new Mail objects.
54
+ # The delivery_method and retriever_method default to :smtp and :pop3, with defaults
55
+ # set.
56
+ #
57
+ # So sending a new email, if you have an SMTP server running on localhost is
58
+ # as easy as:
59
+ #
60
+ # Mail.deliver do
61
+ # to 'mikel@test.lindsaar.net'
62
+ # from 'bob@test.lindsaar.net'
63
+ # subject 'hi there!'
64
+ # body 'this is a body'
65
+ # end
66
+ #
67
+ # If you do not specify anything, you will get the following equivalent code set in
68
+ # every new mail object:
69
+ #
70
+ # Mail.defaults do
71
+ # delivery_method :smtp, { :address => "localhost",
72
+ # :port => 25,
73
+ # :domain => 'localhost.localdomain',
74
+ # :user_name => nil,
75
+ # :password => nil,
76
+ # :authentication => nil,
77
+ # :enable_starttls_auto => true }
78
+ #
79
+ # retriever_method :pop3, { :address => "localhost",
80
+ # :port => 995,
81
+ # :user_name => nil,
82
+ # :password => nil,
83
+ # :enable_ssl => true }
84
+ # end
85
+ #
86
+ # Mail.delivery_method.new #=> Mail::SMTP instance
87
+ # Mail.retriever_method.new #=> Mail::POP3 instance
88
+ #
89
+ # Each mail object inherits the default set in Mail.delivery_method, however, on
90
+ # a per email basis, you can override the method:
91
+ #
92
+ # mail.delivery_method :sendmail
93
+ #
94
+ # Or you can override the method and pass in settings:
95
+ #
96
+ # mail.delivery_method :sendmail, { :address => 'some.host' }
97
+ #
98
+ # You can also just modify the settings:
99
+ #
100
+ # mail.delivery_settings = { :address => 'some.host' }
101
+ #
102
+ # The passed in hash is just merged against the defaults with +merge!+ and the result
103
+ # assigned the mail object. So the above example will change only the :address value
104
+ # of the global smtp_settings to be 'some.host', keeping all other values
105
+ def self.defaults(&block)
106
+ Configuration.instance.instance_eval(&block)
107
+ end
108
+
109
+ # Returns the delivery method selected, defaults to an instance of Mail::SMTP
110
+ def self.delivery_method
111
+ Configuration.instance.delivery_method
112
+ end
113
+
114
+ # Returns the retriever method selected, defaults to an instance of Mail::POP3
115
+ def self.retriever_method
116
+ Configuration.instance.retriever_method
117
+ end
118
+
119
+ # Send an email using the default configuration. You do need to set a default
120
+ # configuration first before you use self.deliver, if you don't, an appropriate
121
+ # error will be raised telling you to.
122
+ #
123
+ # If you do not specify a delivery type, SMTP will be used.
124
+ #
125
+ # Mail.deliver do
126
+ # to 'mikel@test.lindsaar.net'
127
+ # from 'ada@test.lindsaar.net'
128
+ # subject 'This is a test email'
129
+ # body 'Not much to say here'
130
+ # end
131
+ #
132
+ # You can also do:
133
+ #
134
+ # mail = Mail.read('email.eml')
135
+ # mail.deliver!
136
+ #
137
+ # And your email object will be created and sent.
138
+ def self.deliver(*args, &block)
139
+ mail = self.new(args, &block)
140
+ mail.deliver
141
+ mail
142
+ end
143
+
144
+ # Find emails from the default retriever
145
+ # See Mail::Retriever for a complete documentation.
146
+ def self.find(*args, &block)
147
+ retriever_method.find(*args, &block)
148
+ end
149
+
150
+ # Finds and then deletes retrieved emails from the default retriever
151
+ # See Mail::Retriever for a complete documentation.
152
+ def self.find_and_delete(*args, &block)
153
+ retriever_method.find_and_delete(*args, &block)
154
+ end
155
+
156
+ # Receive the first email(s) from the default retriever
157
+ # See Mail::Retriever for a complete documentation.
158
+ def self.first(*args, &block)
159
+ retriever_method.first(*args, &block)
160
+ end
161
+
162
+ # Receive the first email(s) from the default retriever
163
+ # See Mail::Retriever for a complete documentation.
164
+ def self.last(*args, &block)
165
+ retriever_method.last(*args, &block)
166
+ end
167
+
168
+ # Receive all emails from the default retriever
169
+ # See Mail::Retriever for a complete documentation.
170
+ def self.all(*args, &block)
171
+ retriever_method.all(*args, &block)
172
+ end
173
+
174
+ # Reads in an email message from a path and instantiates it as a new Mail::Message
175
+ def self.read(filename)
176
+ self.new(File.open(filename, 'rb') { |f| f.read })
177
+ end
178
+
179
+ # Delete all emails from the default retriever
180
+ # See Mail::Retriever for a complete documentation.
181
+ def self.delete_all(*args, &block)
182
+ retriever_method.delete_all(*args, &block)
183
+ end
184
+
185
+ # Instantiates a new Mail::Message using a string
186
+ def Mail.read_from_string(mail_as_string)
187
+ Mail.new(mail_as_string)
188
+ end
189
+
190
+ def Mail.connection(&block)
191
+ retriever_method.connection(&block)
192
+ end
193
+
194
+ # Initialize the observers and interceptors arrays
195
+ @@delivery_notification_observers = []
196
+ @@delivery_interceptors = []
197
+
198
+ # You can register an object to be informed of every email that is sent through
199
+ # this method.
200
+ #
201
+ # Your object needs to respond to a single method #delivered_email(mail)
202
+ # which receives the email that is sent.
203
+ def self.register_observer(observer)
204
+ unless @@delivery_notification_observers.include?(observer)
205
+ @@delivery_notification_observers << observer
206
+ end
207
+ end
208
+
209
+ # Unregister the given observer, allowing mail to resume operations
210
+ # without it.
211
+ def self.unregister_observer(observer)
212
+ @@delivery_notification_observers.delete(observer)
213
+ end
214
+
215
+ # You can register an object to be given every mail object that will be sent,
216
+ # before it is sent. So if you want to add special headers or modify any
217
+ # email that gets sent through the Mail library, you can do so.
218
+ #
219
+ # Your object needs to respond to a single method #delivering_email(mail)
220
+ # which receives the email that is about to be sent. Make your modifications
221
+ # directly to this object.
222
+ def self.register_interceptor(interceptor)
223
+ unless @@delivery_interceptors.include?(interceptor)
224
+ @@delivery_interceptors << interceptor
225
+ end
226
+ end
227
+
228
+ # Unregister the given interceptor, allowing mail to resume operations
229
+ # without it.
230
+ def self.unregister_interceptor(interceptor)
231
+ @@delivery_interceptors.delete(interceptor)
232
+ end
233
+
234
+ def self.inform_observers(mail)
235
+ @@delivery_notification_observers.each do |observer|
236
+ observer.delivered_email(mail)
237
+ end
238
+ end
239
+
240
+ def self.inform_interceptors(mail)
241
+ @@delivery_interceptors.each do |interceptor|
242
+ interceptor.delivering_email(mail)
243
+ end
244
+ end
245
+
246
+ protected
247
+
248
+ def self.random_tag
249
+ t = Time.now
250
+ sprintf('%x%x_%x%x%d%x',
251
+ t.to_i, t.tv_usec,
252
+ $$, Thread.current.object_id.abs, self.uniq, rand(255))
253
+ end
254
+
255
+ private
256
+
257
+ def self.something_random
258
+ (Thread.current.object_id * rand(255) / Time.now.to_f).to_s.slice(-3..-1).to_i
259
+ end
260
+
261
+ def self.uniq
262
+ @@uniq += 1
263
+ end
264
+
265
+ @@uniq = self.something_random
266
+
267
+ end
@@ -0,0 +1,157 @@
1
+ module Mail
2
+ module Matchers
3
+ def have_sent_email
4
+ HasSentEmailMatcher.new(self)
5
+ end
6
+
7
+ class HasSentEmailMatcher
8
+ def initialize(_context)
9
+ end
10
+
11
+ def matches?(subject)
12
+ matching_deliveries = filter_matched_deliveries(Mail::TestMailer.deliveries)
13
+ !(matching_deliveries.empty?)
14
+ end
15
+
16
+ def from(sender)
17
+ @sender = sender
18
+ self
19
+ end
20
+
21
+ def to(recipient_or_list)
22
+ @recipients ||= []
23
+
24
+ if recipient_or_list.kind_of?(Array)
25
+ @recipients += recipient_or_list
26
+ else
27
+ @recipients << recipient_or_list
28
+ end
29
+ self
30
+ end
31
+
32
+ def cc(recipient_or_list)
33
+ @copy_recipients ||= []
34
+
35
+ if recipient_or_list.kind_of?(Array)
36
+ @copy_recipients += recipient_or_list
37
+ else
38
+ @copy_recipients << recipient_or_list
39
+ end
40
+ self
41
+ end
42
+
43
+ def bcc(recipient_or_list)
44
+ @blind_copy_recipients ||= []
45
+
46
+ if recipient_or_list.kind_of?(Array)
47
+ @blind_copy_recipients += recipient_or_list
48
+ else
49
+ @blind_copy_recipients << recipient_or_list
50
+ end
51
+ self
52
+ end
53
+
54
+
55
+ def with_subject(subject)
56
+ @subject = subject
57
+ self
58
+ end
59
+
60
+ def matching_subject(subject_matcher)
61
+ @subject_matcher = subject_matcher
62
+ self
63
+ end
64
+
65
+ def with_body(body)
66
+ @body = body
67
+ self
68
+ end
69
+
70
+ def matching_body(body_matcher)
71
+ @body_matcher = body_matcher
72
+ self
73
+ end
74
+
75
+ def description
76
+ result = "send a matching email"
77
+ result
78
+ end
79
+
80
+ def failure_message
81
+ result = "Expected email to be sent "
82
+ result += explain_expectations
83
+ result += dump_deliveries
84
+ result
85
+ end
86
+
87
+ def negative_failure_message
88
+ result = "Expected no email to be sent "
89
+ result += explain_expectations
90
+ result += dump_deliveries
91
+ result
92
+ end
93
+
94
+ protected
95
+
96
+ def filter_matched_deliveries(deliveries)
97
+ candidate_deliveries = deliveries
98
+
99
+ %w(sender recipients copy_recipients blind_copy_recipients subject subject_matcher body body_matcher).each do |modifier_name|
100
+ next unless instance_variable_defined?("@#{modifier_name}")
101
+ candidate_deliveries = candidate_deliveries.select{|matching_delivery| self.send("matches_on_#{modifier_name}?", matching_delivery)}
102
+ end
103
+
104
+ candidate_deliveries
105
+ end
106
+
107
+ def matches_on_sender?(delivery)
108
+ delivery.from.include?(@sender)
109
+ end
110
+
111
+ def matches_on_recipients?(delivery)
112
+ @recipients.all? {|recipient| delivery.to.include?(recipient) }
113
+ end
114
+
115
+ def matches_on_copy_recipients?(delivery)
116
+ @copy_recipients.all? {|recipient| delivery.cc.include?(recipient) }
117
+ end
118
+
119
+ def matches_on_blind_copy_recipients?(delivery)
120
+ @blind_copy_recipients.all? {|recipient| delivery.bcc.include?(recipient) }
121
+ end
122
+
123
+ def matches_on_subject?(delivery)
124
+ delivery.subject == @subject
125
+ end
126
+
127
+ def matches_on_subject_matcher?(delivery)
128
+ @subject_matcher.match delivery.subject
129
+ end
130
+
131
+ def matches_on_body?(delivery)
132
+ delivery.body == @body
133
+ end
134
+
135
+ def matches_on_body_matcher?(delivery)
136
+ @body_matcher.match delivery.body.raw_source
137
+ end
138
+
139
+ def explain_expectations
140
+ result = ''
141
+ result += "from #{@sender} " if instance_variable_defined?('@sender')
142
+ result += "to #{@recipients.inspect} " if instance_variable_defined?('@recipients')
143
+ result += "cc #{@copy_recipients.inspect} " if instance_variable_defined?('@copy_recipients')
144
+ result += "bcc #{@blind_copy_recipients.inspect} " if instance_variable_defined?('@blind_copy_recipients')
145
+ result += "with subject \"#{@subject}\" " if instance_variable_defined?('@subject')
146
+ result += "with subject matching \"#{@subject_matcher}\" " if instance_variable_defined?('@subject_matcher')
147
+ result += "with body \"#{@body}\" " if instance_variable_defined?('@body')
148
+ result += "with body matching \"#{@body_matcher}\" " if instance_variable_defined?('@body_matcher')
149
+ result
150
+ end
151
+
152
+ def dump_deliveries
153
+ "(actual deliveries: " + Mail::TestMailer.deliveries.inspect + ")"
154
+ end
155
+ end
156
+ end
157
+ end