mail 1.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of mail might be problematic. Click here for more details.

Files changed (107) hide show
  1. data/.gitignore +4 -0
  2. data/Manifest.txt +106 -0
  3. data/README.rdoc +441 -0
  4. data/Rakefile +38 -0
  5. data/lib/mail.rb +86 -0
  6. data/lib/mail/attachment.rb +90 -0
  7. data/lib/mail/body.rb +149 -0
  8. data/lib/mail/configuration.rb +90 -0
  9. data/lib/mail/core_extensions.rb +6 -0
  10. data/lib/mail/core_extensions/blank.rb +41 -0
  11. data/lib/mail/core_extensions/nil.rb +15 -0
  12. data/lib/mail/core_extensions/string.rb +31 -0
  13. data/lib/mail/elements/address.rb +293 -0
  14. data/lib/mail/elements/address_list.rb +62 -0
  15. data/lib/mail/elements/content_disposition_element.rb +34 -0
  16. data/lib/mail/elements/content_transfer_encoding_element.rb +21 -0
  17. data/lib/mail/elements/content_type_element.rb +39 -0
  18. data/lib/mail/elements/date_time_element.rb +26 -0
  19. data/lib/mail/elements/envelope_from_element.rb +34 -0
  20. data/lib/mail/elements/message_ids_element.rb +29 -0
  21. data/lib/mail/elements/mime_version_element.rb +26 -0
  22. data/lib/mail/elements/phrase_list.rb +21 -0
  23. data/lib/mail/elements/received_element.rb +30 -0
  24. data/lib/mail/encodings/base64.rb +17 -0
  25. data/lib/mail/encodings/encodings.rb +24 -0
  26. data/lib/mail/encodings/quoted_printable.rb +26 -0
  27. data/lib/mail/envelope.rb +35 -0
  28. data/lib/mail/field.rb +202 -0
  29. data/lib/mail/field_list.rb +33 -0
  30. data/lib/mail/fields/bcc_field.rb +40 -0
  31. data/lib/mail/fields/cc_field.rb +40 -0
  32. data/lib/mail/fields/comments_field.rb +41 -0
  33. data/lib/mail/fields/common/common_address.rb +62 -0
  34. data/lib/mail/fields/common/common_date.rb +35 -0
  35. data/lib/mail/fields/common/common_field.rb +128 -0
  36. data/lib/mail/fields/common/common_message_id.rb +35 -0
  37. data/lib/mail/fields/content_description_field.rb +15 -0
  38. data/lib/mail/fields/content_disposition_field.rb +34 -0
  39. data/lib/mail/fields/content_id_field.rb +50 -0
  40. data/lib/mail/fields/content_transfer_encoding_field.rb +28 -0
  41. data/lib/mail/fields/content_type_field.rb +50 -0
  42. data/lib/mail/fields/date_field.rb +44 -0
  43. data/lib/mail/fields/from_field.rb +40 -0
  44. data/lib/mail/fields/in_reply_to_field.rb +42 -0
  45. data/lib/mail/fields/keywords_field.rb +22 -0
  46. data/lib/mail/fields/message_id_field.rb +70 -0
  47. data/lib/mail/fields/mime_version_field.rb +42 -0
  48. data/lib/mail/fields/optional_field.rb +11 -0
  49. data/lib/mail/fields/received_field.rb +49 -0
  50. data/lib/mail/fields/references_field.rb +42 -0
  51. data/lib/mail/fields/reply_to_field.rb +40 -0
  52. data/lib/mail/fields/resent_bcc_field.rb +40 -0
  53. data/lib/mail/fields/resent_cc_field.rb +40 -0
  54. data/lib/mail/fields/resent_date_field.rb +16 -0
  55. data/lib/mail/fields/resent_from_field.rb +40 -0
  56. data/lib/mail/fields/resent_message_id_field.rb +20 -0
  57. data/lib/mail/fields/resent_sender_field.rb +48 -0
  58. data/lib/mail/fields/resent_to_field.rb +40 -0
  59. data/lib/mail/fields/return_path_field.rb +34 -0
  60. data/lib/mail/fields/sender_field.rb +48 -0
  61. data/lib/mail/fields/structured_field.rb +32 -0
  62. data/lib/mail/fields/subject_field.rb +14 -0
  63. data/lib/mail/fields/to_field.rb +40 -0
  64. data/lib/mail/fields/unstructured_field.rb +27 -0
  65. data/lib/mail/header.rb +213 -0
  66. data/lib/mail/mail.rb +120 -0
  67. data/lib/mail/message.rb +648 -0
  68. data/lib/mail/network/deliverable.rb +42 -0
  69. data/lib/mail/network/retrievable.rb +63 -0
  70. data/lib/mail/parsers/address_lists.rb +61 -0
  71. data/lib/mail/parsers/address_lists.treetop +19 -0
  72. data/lib/mail/parsers/content_disposition.rb +358 -0
  73. data/lib/mail/parsers/content_disposition.treetop +45 -0
  74. data/lib/mail/parsers/content_transfer_encoding.rb +179 -0
  75. data/lib/mail/parsers/content_transfer_encoding.treetop +25 -0
  76. data/lib/mail/parsers/content_type.rb +507 -0
  77. data/lib/mail/parsers/content_type.treetop +58 -0
  78. data/lib/mail/parsers/date_time.rb +111 -0
  79. data/lib/mail/parsers/date_time.treetop +11 -0
  80. data/lib/mail/parsers/envelope_from.rb +188 -0
  81. data/lib/mail/parsers/envelope_from.treetop +32 -0
  82. data/lib/mail/parsers/message_ids.rb +42 -0
  83. data/lib/mail/parsers/message_ids.treetop +15 -0
  84. data/lib/mail/parsers/mime_version.rb +141 -0
  85. data/lib/mail/parsers/mime_version.treetop +19 -0
  86. data/lib/mail/parsers/phrase_lists.rb +42 -0
  87. data/lib/mail/parsers/phrase_lists.treetop +15 -0
  88. data/lib/mail/parsers/received.rb +68 -0
  89. data/lib/mail/parsers/received.treetop +11 -0
  90. data/lib/mail/parsers/rfc2045.rb +406 -0
  91. data/lib/mail/parsers/rfc2045.treetop +35 -0
  92. data/lib/mail/parsers/rfc2822.rb +5005 -0
  93. data/lib/mail/parsers/rfc2822.treetop +402 -0
  94. data/lib/mail/parsers/rfc2822_obsolete.rb +3607 -0
  95. data/lib/mail/parsers/rfc2822_obsolete.treetop +241 -0
  96. data/lib/mail/part.rb +120 -0
  97. data/lib/mail/patterns.rb +42 -0
  98. data/lib/mail/utilities.rb +142 -0
  99. data/lib/mail/version.rb +10 -0
  100. data/lib/mail/version_specific/multibyte.rb +62 -0
  101. data/lib/mail/version_specific/multibyte/chars.rb +701 -0
  102. data/lib/mail/version_specific/multibyte/exceptions.rb +8 -0
  103. data/lib/mail/version_specific/multibyte/unicode_database.rb +71 -0
  104. data/lib/mail/version_specific/ruby_1_8.rb +61 -0
  105. data/lib/mail/version_specific/ruby_1_8_string.rb +88 -0
  106. data/lib/mail/version_specific/ruby_1_9.rb +49 -0
  107. metadata +192 -0
@@ -0,0 +1,241 @@
1
+ module Mail
2
+ grammar RFC2822Obsolete
3
+
4
+ rule obs_qp
5
+ "\\" [\x00-\x7F]
6
+ end
7
+
8
+ rule obs_text
9
+ LF* CR* (obs_char LF* CR*)*
10
+ end
11
+
12
+ rule obs_char
13
+ [\x00-\x09] / # %d0-127 except CR and
14
+ [\x0B-\x0C] / # LF
15
+ [\x0E-\x7F]
16
+ end
17
+
18
+ rule obs_utext
19
+ obs_text
20
+ end
21
+
22
+ rule obs_phrase
23
+ (word / ".")+
24
+ end
25
+
26
+ rule obs_phrase_list
27
+ phrase / (phrase? CFWS? "," CFWS?)+ phrase?
28
+ end
29
+
30
+ rule obs_FWS
31
+ WSP+ (CRLF WSP+)*
32
+ end
33
+
34
+ rule obs_day_of_week
35
+ CFWS? day_name CFWS?
36
+ end
37
+
38
+ rule obs_year
39
+ CFWS? (DIGIT DIGIT) CFWS?
40
+ end
41
+
42
+ rule obs_month
43
+ CFWS month_name CFWS
44
+ end
45
+
46
+ rule obs_day
47
+ CFWS? (DIGIT / (DIGIT DIGIT)) CFWS?
48
+ end
49
+
50
+ rule obs_hour
51
+ CFWS? (DIGIT DIGIT) CFWS?
52
+ end
53
+
54
+ rule obs_minute
55
+ CFWS? (DIGIT DIGIT) CFWS?
56
+ end
57
+
58
+ rule obs_second
59
+ CFWS? (DIGIT DIGIT) CFWS?
60
+ end
61
+
62
+ rule obs_zone
63
+ "UT" / "GMT" / # Universal Time
64
+ # North American UT
65
+ # offsets
66
+ "EST" / "EDT" / # Eastern: - 5/ - 4
67
+ "CST" / "CDT" / # Central: - 6/ - 5
68
+ "MST" / "MDT" / # Mountain: - 7/ - 6
69
+ "PST" / "PDT" / # Pacific: - 8/ - 7
70
+ #
71
+ [\x41-\x49] / # Military zones - "A"
72
+ [\x4B-\x5A] / # through "I" and "K"
73
+ [\x61-\x69] / # through "Z", both
74
+ [\x6B-\x7A] # upper and lower case
75
+ end
76
+
77
+ rule obs_angle_addr
78
+ CFWS? "<" obs_route? addr_spec ">" CFWS?
79
+ end
80
+
81
+ rule obs_route
82
+ CFWS? obs_domain_list ":" CFWS?
83
+ end
84
+
85
+ rule obs_domain_list
86
+ "@" domain (("," )* CFWS? "@" domain)*
87
+ end
88
+
89
+ rule obs_local_part
90
+ word ("." word)*
91
+ end
92
+
93
+ rule obs_domain
94
+ atom ("." atom)*
95
+ end
96
+
97
+ rule obs_mbox_list
98
+ (mailbox? CFWS? "," CFWS?)+ mailbox?
99
+ end
100
+
101
+ rule obs_addr_list
102
+ (address? CFWS? "," CFWS?)+ address?
103
+ end
104
+
105
+ rule obs_fields
106
+ (obs_return /
107
+ obs_received /
108
+ obs_orig_date /
109
+ obs_from /
110
+ obs_sender /
111
+ obs_reply_to /
112
+ obs_to /
113
+ obs_cc /
114
+ obs_bcc /
115
+ obs_message_id /
116
+ obs_in_reply_to /
117
+ obs_references /
118
+ obs_subject /
119
+ obs_comments /
120
+ obs_keywords /
121
+ obs_resent_date /
122
+ obs_resent_from /
123
+ obs_resent_send /
124
+ obs_resent_rply /
125
+ obs_resent_to /
126
+ obs_resent_cc /
127
+ obs_resent_bcc /
128
+ obs_resent_mid /
129
+ obs_optional)*
130
+ end
131
+
132
+ rule obs_orig_date
133
+ "Date" WSP* ":" date_time CRLF
134
+ end
135
+
136
+ rule obs_from
137
+ "From" WSP* ":" mailbox_list CRLF
138
+ end
139
+
140
+ rule obs_sender
141
+ "Sender" WSP* ":" mailbox CRLF
142
+ end
143
+
144
+ rule obs_reply_to
145
+ "Reply-To" WSP* ":" mailbox_list CRLF
146
+ end
147
+
148
+
149
+ rule obs_to
150
+ "To" WSP* ":" address_list CRLF
151
+ end
152
+
153
+ rule obs_cc
154
+ "Cc" WSP* ":" address_list CRLF
155
+ end
156
+
157
+ rule obs_bcc
158
+ "Bcc" WSP* ":" (address_list / CFWS?) CRLF
159
+ end
160
+
161
+ rule obs_message_id
162
+ "Message-ID" WSP* ":" msg_id CRLF
163
+ end
164
+
165
+ rule obs_in_reply_to
166
+ "In-Reply-To" WSP* ":" (phrase / msg_id)* CRLF
167
+ end
168
+
169
+ rule obs_references
170
+ "References" WSP* ":" (phrase / msg_id)* CRLF
171
+ end
172
+
173
+ rule obs_id_left
174
+ local_part
175
+ end
176
+
177
+ rule obs_id_right
178
+ domain
179
+ end
180
+
181
+ rule obs_subject
182
+ "Subject" WSP* ":" unstructured CRLF
183
+ end
184
+
185
+ rule obs_comments
186
+ "Comments" WSP* ":" unstructured CRLF
187
+ end
188
+
189
+ rule obs_keywords
190
+ "Keywords" WSP* ":" obs_phrase_list CRLF
191
+ end
192
+
193
+ rule obs_resent_from
194
+ "Resent-From" WSP* ":" mailbox_list CRLF
195
+ end
196
+
197
+ rule obs_resent_send
198
+ "Resent-Sender" WSP* ":" mailbox CRLF
199
+ end
200
+
201
+ rule obs_resent_date
202
+ "Resent-Date" WSP* ":" date_time CRLF
203
+ end
204
+
205
+ rule obs_resent_to
206
+ "Resent-To" WSP* ":" address_list CRLF
207
+ end
208
+
209
+ rule obs_resent_cc
210
+ "Resent-Cc" WSP* ":" address_list CRLF
211
+ end
212
+
213
+ rule obs_resent_bcc
214
+ "Resent-Bcc" WSP* ":" (address_list / CFWS?) CRLF
215
+ end
216
+
217
+ rule obs_resent_mid
218
+ "Resent-Message-ID" WSP* ":" msg_id CRLF
219
+ end
220
+
221
+ rule obs_resent_rply
222
+ "Resent-Reply-To" WSP* ":" address_list CRLF
223
+ end
224
+
225
+ rule obs_return
226
+ "Return-Path" WSP* ":" path CRLF
227
+ end
228
+
229
+ rule obs_received
230
+ "Received" WSP* ":" name_val_list CRLF
231
+ end
232
+
233
+ rule obs_path
234
+ obs_angle_addr
235
+ end
236
+
237
+ rule obs_optional
238
+ field_name WSP* ":" unstructured CRLF
239
+ end
240
+ end
241
+ end
@@ -0,0 +1,120 @@
1
+ # encoding: utf-8
2
+ module Mail
3
+ class Part < Message
4
+
5
+ def initialize(*args, &block)
6
+ if args.flatten[0].is_a?(Hash)
7
+ options_hash = args.flatten[0]
8
+ super('') # Make an empty message, we are dealing with an attachment
9
+ @attachment = Mail::Attachment.new(options_hash)
10
+ self.content_type = "#{attachment.mime_type}; filename=\"#{attachment.filename}\""
11
+ self.content_transfer_encoding = "Base64"
12
+ self.content_disposition = "attachment; filename=\"#{attachment.filename}\""
13
+ self.body = attachment.encoded
14
+ else
15
+ super
16
+ if content_type.parameters['filename']
17
+ @attachment = Mail::Attachment.new(:filename => content_type.parameters['filename'],
18
+ :data => body.to_s,
19
+ :encoding => content_transfer_encoding.to_s)
20
+ end
21
+ end
22
+ end
23
+
24
+ # Creates a new empty Content-ID field and inserts it in the correct order
25
+ # into the Header. The ContentIdField object will automatically generate
26
+ # a unique content ID if you try and encode it or output it to_s without
27
+ # specifying a content id.
28
+ #
29
+ # It will preserve the content ID you specify if you do.
30
+ def add_content_id(content_id_val = '')
31
+ header['content-id'] = content_id_val
32
+ end
33
+
34
+ # Returns true if this part is an attachment
35
+ def attachment?
36
+ @attachment ? true : false
37
+ end
38
+
39
+ # Returns the attachment data if there is any
40
+ def attachment
41
+ @attachment
42
+ end
43
+
44
+ # Returns the filename of the attachment
45
+ def filename
46
+ if attachment?
47
+ attachment.filename
48
+ else
49
+ nil
50
+ end
51
+ end
52
+
53
+ # Returns true if the part has a content ID field, the field may or may
54
+ # not have a value, but the field exists or not.
55
+ def has_content_id?
56
+ header.has_content_id?
57
+ end
58
+
59
+ def add_required_fields
60
+ add_content_id unless has_content_id?
61
+ super
62
+ end
63
+
64
+ def delivery_status_report_part?
65
+ main_type =~ /message/i && sub_type =~ /delivery-status/i
66
+ end
67
+
68
+ def delivery_status_data
69
+ delivery_status_report_part? ? parse_delivery_status_report : {}
70
+ end
71
+
72
+ def bounced?
73
+ (action =~ /failed/i)
74
+ end
75
+
76
+ def action
77
+ delivery_status_data['action'].value
78
+ end
79
+
80
+ def final_recipient
81
+ delivery_status_data['final-recipient'].value
82
+ end
83
+
84
+ def error_status
85
+ delivery_status_data['status'].value
86
+ end
87
+
88
+ def diagnostic_code
89
+ delivery_status_data['diagnostic-code'].value
90
+ end
91
+
92
+ def remote_mta
93
+ delivery_status_data['remote-mta'].value
94
+ end
95
+
96
+ def retryable?
97
+ !(error_status =~ /^5/)
98
+ end
99
+
100
+ private
101
+
102
+ # A part may not have a header.... so, just init a body if no header
103
+ def parse_message
104
+ header_part, body_part = raw_source.split(/#{CRLF}#{WSP}*#{CRLF}/m, 2)
105
+ if header_part =~ HEADER_LINE
106
+ self.header = header_part
107
+ self.body = body_part
108
+ else
109
+ self.header = "Content-Type: text/plain\r\n"
110
+ self.body = header_part
111
+ end
112
+ end
113
+
114
+ def parse_delivery_status_report
115
+ @delivery_status_data ||= Header.new(body.to_s.gsub("\r\n\r\n", "\r\n"))
116
+ end
117
+
118
+ end
119
+
120
+ end
@@ -0,0 +1,42 @@
1
+ module Mail
2
+ module Patterns
3
+
4
+ white_space = %Q|\x9\x20|
5
+ text = %Q|\x1-\x8\xB\xC\xE-\x7f|
6
+ field_name = %Q|\x21-\x39\x3b-\x7e|
7
+ field_body = text
8
+
9
+ aspecial = %Q|()<>[]:;.\\,"|
10
+ tspecial = %Q|()<>[];:\\,"/?=|
11
+ lwsp = %Q| \t\r\n|
12
+ control = %Q|\x00-\x1f\x7f-\xff|
13
+
14
+ CRLF = /\r\n/
15
+ WSP = /[#{white_space}]/
16
+ FWS = /#{CRLF}#{WSP}*/
17
+ TEXT = /[#{text}]/ # + obs-text
18
+ FIELD_NAME = /[#{field_name}]+/
19
+ FIELD_BODY = /[#{field_body}]+/
20
+ FIELD_LINE = /^[#{field_name}]+:\s*[#{field_body}]+$/
21
+ HEADER_LINE = /^([#{field_name}]+:\s*[#{field_body}]+)/
22
+
23
+ CONTROL_CHAR = /[#{control}]/n
24
+ ATOM_UNSAFE = /[#{Regexp.quote aspecial}#{control}#{lwsp}]/n
25
+ PHRASE_UNSAFE = /[#{Regexp.quote aspecial}#{control}]/n
26
+ TOKEN_UNSAFE = /[#{Regexp.quote tspecial}#{control}#{lwsp}]/n
27
+
28
+ module ClassMethods
29
+
30
+ end
31
+
32
+ module InstanceMethods
33
+
34
+ end
35
+
36
+ def self.included(receiver)
37
+ receiver.extend ClassMethods
38
+ receiver.send :include, InstanceMethods
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,142 @@
1
+ # encoding: utf-8
2
+ module Mail
3
+ module Utilities
4
+
5
+ module ClassMethods # :nodoc:
6
+
7
+ end
8
+
9
+ module InstanceMethods
10
+
11
+ include Patterns
12
+
13
+ # Returns true if the string supplied is free from characters not allowed as an ATOM
14
+ def atom_safe?( str )
15
+ not ATOM_UNSAFE === str
16
+ end
17
+
18
+ # If the string supplied has ATOM unsafe characters in it, will return the string quoted
19
+ # in double quotes, otherwise returns the string unmodified
20
+ def quote_atom( str )
21
+ (ATOM_UNSAFE === str) ? dquote(str) : str
22
+ end
23
+
24
+ # If the string supplied has PHRASE unsafe characters in it, will return the string quoted
25
+ # in double quotes, otherwise returns the string unmodified
26
+ def quote_phrase( str )
27
+ (PHRASE_UNSAFE === str) ? dquote(str) : str
28
+ end
29
+
30
+ # Returns true if the string supplied is free from characters not allowed as a TOKEN
31
+ def token_safe?( str )
32
+ not TOKEN_UNSAFE === str
33
+ end
34
+
35
+ # If the string supplied has TOKEN unsafe characters in it, will return the string quoted
36
+ # in double quotes, otherwise returns the string unmodified
37
+ def quote_token( str )
38
+ (TOKEN_UNSAFE === str) ? dquote(str) : str
39
+ end
40
+
41
+ # Wraps supplied string in double quotes unless it is already wrapped.
42
+ #
43
+ # Additionally will escape any double quotation marks in the string with a single
44
+ # backslash in front of the '"' character.
45
+ def dquote( str )
46
+ str = $1 if str =~ /^"(.*)?"$/
47
+ # First remove all escaped double quotes:
48
+ str = str.gsub(/\\"/, '"')
49
+ # Then wrap and re-escape all double quotes
50
+ '"' + str.gsub(/["]/n) {|s| '\\' + s } + '"'
51
+ end
52
+
53
+ # Unwraps supplied string from inside double quotes.
54
+ #
55
+ # Example:
56
+ #
57
+ # string = '"This is a string"'
58
+ # unquote(string) #=> 'This is a string'
59
+ def unquote( str )
60
+ str =~ /^"(.*?)"$/ ? $1 : str
61
+ end
62
+
63
+ # Wraps a string in parenthesis and escapes any that are in the string itself.
64
+ #
65
+ # Example:
66
+ #
67
+ # string
68
+ def paren( str )
69
+ RubyVer.paren( str )
70
+ end
71
+
72
+ # Unwraps a string from being wrapped in parenthesis
73
+ #
74
+ # Example:
75
+ #
76
+ # str = '(This is a string)'
77
+ # unparen( str ) #=> 'This is a string'
78
+ def unparen( str )
79
+ str =~ /^\((.*?)\)$/ ? $1 : str
80
+ end
81
+
82
+ # Escape parenthesies in a string
83
+ #
84
+ # Example:
85
+ #
86
+ # str = 'This is (a) string'
87
+ # escape_paren( str ) #=> 'This is \(a\) string'
88
+ def escape_paren( str )
89
+ RubyVer.escape_paren( str )
90
+ end
91
+
92
+ # Matches two objects with their to_s values case insensitively
93
+ #
94
+ # Example:
95
+ #
96
+ # obj2 = "This_is_An_object"
97
+ # obj1 = :this_IS_an_object
98
+ # match_to_s( obj1, obj2 ) #=> true
99
+ def match_to_s( obj1, obj2 )
100
+ obj1.to_s.downcase == obj2.to_s.downcase
101
+ end
102
+
103
+ # Capitalizes a string that is joined by hyphens correctly.
104
+ #
105
+ # Example:
106
+ #
107
+ # string = 'resent-from-field'
108
+ # capitalize_field( string ) #=> 'Resent-From-Field'
109
+ def capitalize_field( str )
110
+ str.to_s.split("-").map { |v| v.capitalize }.join("-")
111
+ end
112
+
113
+ # Takes an underscored word and turns it into a class name
114
+ #
115
+ # Example:
116
+ #
117
+ # constantize("hello") #=> "Hello"
118
+ # constantize("hello-there") #=> "HelloThere"
119
+ # constantize("hello-there-mate") #=> "HelloThereMate"
120
+ def constantize( str )
121
+ str.to_s.split(/[-_]/).map { |v| v.capitalize }.to_s
122
+ end
123
+
124
+ # Swaps out all hyphens (-) for underscores (_) good for symbolizing
125
+ # a field name.
126
+ #
127
+ # Example:
128
+ #
129
+ # string = 'Resent-From-Field'
130
+ # underscoreize ( string ) #=> 'resent_from_field'
131
+ def underscoreize( str )
132
+ str.to_s.downcase.gsub('_', '-')
133
+ end
134
+
135
+ end
136
+
137
+ def self.included(receiver) # :nodoc:
138
+ receiver.extend ClassMethods
139
+ receiver.send :include, InstanceMethods
140
+ end
141
+ end
142
+ end