net-imap 0.4.12 → 0.5.5
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of net-imap might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Gemfile +7 -1
- data/README.md +10 -4
- data/docs/styles.css +75 -14
- data/lib/net/imap/authenticators.rb +2 -2
- data/lib/net/imap/command_data.rb +61 -48
- data/lib/net/imap/config/attr_accessors.rb +75 -0
- data/lib/net/imap/config/attr_inheritance.rb +90 -0
- data/lib/net/imap/config/attr_type_coercion.rb +61 -0
- data/lib/net/imap/config.rb +402 -0
- data/lib/net/imap/data_encoding.rb +3 -3
- data/lib/net/imap/data_lite.rb +226 -0
- data/lib/net/imap/deprecated_client_options.rb +8 -5
- data/lib/net/imap/errors.rb +6 -0
- data/lib/net/imap/esearch_result.rb +180 -0
- data/lib/net/imap/fetch_data.rb +126 -47
- data/lib/net/imap/response_data.rb +126 -193
- data/lib/net/imap/response_parser/parser_utils.rb +11 -6
- data/lib/net/imap/response_parser.rb +159 -21
- data/lib/net/imap/sasl/anonymous_authenticator.rb +3 -3
- data/lib/net/imap/sasl/authentication_exchange.rb +52 -20
- data/lib/net/imap/sasl/authenticators.rb +8 -4
- data/lib/net/imap/sasl/client_adapter.rb +77 -26
- data/lib/net/imap/sasl/cram_md5_authenticator.rb +4 -4
- data/lib/net/imap/sasl/digest_md5_authenticator.rb +218 -56
- data/lib/net/imap/sasl/external_authenticator.rb +2 -2
- data/lib/net/imap/sasl/gs2_header.rb +7 -7
- data/lib/net/imap/sasl/login_authenticator.rb +4 -3
- data/lib/net/imap/sasl/oauthbearer_authenticator.rb +6 -6
- data/lib/net/imap/sasl/plain_authenticator.rb +7 -7
- data/lib/net/imap/sasl/protocol_adapters.rb +60 -4
- data/lib/net/imap/sasl/scram_authenticator.rb +8 -8
- data/lib/net/imap/sasl.rb +7 -4
- data/lib/net/imap/sasl_adapter.rb +0 -1
- data/lib/net/imap/search_result.rb +2 -2
- data/lib/net/imap/sequence_set.rb +28 -24
- data/lib/net/imap/stringprep/nameprep.rb +1 -1
- data/lib/net/imap/stringprep/trace.rb +4 -4
- data/lib/net/imap/vanished_data.rb +56 -0
- data/lib/net/imap.rb +1001 -319
- data/net-imap.gemspec +3 -3
- data/rakelib/rfcs.rake +2 -0
- data/rakelib/string_prep_tables_generator.rb +2 -0
- metadata +11 -10
- data/.github/dependabot.yml +0 -6
- data/.github/workflows/pages.yml +0 -46
- data/.github/workflows/push_gem.yml +0 -48
- data/.github/workflows/test.yml +0 -31
- data/.gitignore +0 -12
- data/.mailmap +0 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c9b92943c4c4d17210f7374b4f5577f95471038a987540a8cdad2088e6bec25d
|
4
|
+
data.tar.gz: 6a28ce5ca7778cbfa3de48c3451be4d17fe6298a01e2a946725caf022a34dd8c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5a575e282b7cd6828d56360003b1de29e8ca0e3d55ce733c8de250f781413dc8e9e6a90549c14b9ed21bb46e0cdc88f3e75d92aeaed769d594f93056359ee40d
|
7
|
+
data.tar.gz: 87d57464c32eab235e241c74efea7953b9798b9c40a14f66170e751560943f728aabe852d38c746283cdcb4c03914ee375021c49fec67f653113d3330a6732d2
|
data/Gemfile
CHANGED
@@ -13,4 +13,10 @@ gem "rdoc"
|
|
13
13
|
gem "test-unit"
|
14
14
|
gem "test-unit-ruby-core", git: "https://github.com/ruby/test-unit-ruby-core"
|
15
15
|
|
16
|
-
gem "benchmark-driver"
|
16
|
+
gem "benchmark-driver", require: false
|
17
|
+
|
18
|
+
group :test do
|
19
|
+
gem "simplecov", require: false
|
20
|
+
gem "simplecov-html", require: false
|
21
|
+
gem "simplecov-json", require: false
|
22
|
+
end
|
data/README.md
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
# Net::IMAP
|
2
2
|
|
3
3
|
Net::IMAP implements Internet Message Access Protocol (IMAP) client
|
4
|
-
functionality. The protocol is described in
|
4
|
+
functionality. The protocol is described in
|
5
|
+
[RFC3501](https://www.rfc-editor.org/rfc/rfc3501),
|
6
|
+
[RFC9051](https://www.rfc-editor.org/rfc/rfc9051) and various extensions.
|
5
7
|
|
6
8
|
## Installation
|
7
9
|
|
@@ -50,12 +52,16 @@ end
|
|
50
52
|
|
51
53
|
```ruby
|
52
54
|
imap.select('Mail/sent-mail')
|
53
|
-
if
|
55
|
+
if imap.list('Mail/', 'sent-apr03').empty?
|
54
56
|
imap.create('Mail/sent-apr03')
|
55
57
|
end
|
56
58
|
imap.search(["BEFORE", "30-Apr-2003", "SINCE", "1-Apr-2003"]).each do |message_id|
|
57
|
-
imap.
|
58
|
-
|
59
|
+
if imap.capable?(:move) || imap.capable?(:IMAP4rev2)
|
60
|
+
imap.move(message_id, "Mail/sent-apr03")
|
61
|
+
else
|
62
|
+
imap.copy(message_id, "Mail/sent-apr03")
|
63
|
+
imap.store(message_id, "+FLAGS", [:Deleted])
|
64
|
+
end
|
59
65
|
end
|
60
66
|
imap.expunge
|
61
67
|
```
|
data/docs/styles.css
CHANGED
@@ -1,24 +1,85 @@
|
|
1
1
|
/* this is a work in progress. :) */
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
/***********************************************
|
4
|
+
* Method descriptions
|
5
|
+
***********************************************/
|
6
|
+
|
7
|
+
main .method-detail {
|
8
|
+
display: grid;
|
9
|
+
grid-template-columns: 1fr auto;
|
10
|
+
justify-content: space-between;
|
11
|
+
}
|
12
|
+
|
13
|
+
main .method-header,
|
14
|
+
main .method-controls,
|
15
|
+
.attribute-method-heading {
|
6
16
|
padding: 0.5em;
|
7
|
-
border
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
17
|
+
/* border: 1px solid var(--highlight-color); */
|
18
|
+
background: var(--table-header-background-color);
|
19
|
+
line-height: 1.6;
|
20
|
+
}
|
21
|
+
|
22
|
+
.attribute-method-heading .attribute-access-type {
|
23
|
+
float: right;
|
24
|
+
}
|
25
|
+
|
26
|
+
main .method-header {
|
27
|
+
border-right: none;
|
28
|
+
border-radius: 4px 0 0 4px;
|
29
|
+
}
|
30
|
+
|
31
|
+
main .method-heading :any-link {
|
32
|
+
text-decoration: none;
|
33
|
+
}
|
34
|
+
|
35
|
+
main .method-controls {
|
36
|
+
border-left: none;
|
37
|
+
border-radius: 0 4px 4px 0;
|
12
38
|
}
|
13
39
|
|
14
40
|
main .method-description, main .aliases {
|
41
|
+
grid-column: 1 / span 2;
|
15
42
|
padding-left: 1em;
|
16
43
|
}
|
17
44
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
45
|
+
@media (max-width: 700px) {
|
46
|
+
main .method-header, main .method-controls, main .method-description {
|
47
|
+
grid-column: 1 / span 2;
|
48
|
+
margin: 0;
|
49
|
+
}
|
50
|
+
main .method-controls {
|
51
|
+
background: none;
|
52
|
+
}
|
53
|
+
}
|
54
|
+
|
55
|
+
/***********************************************
|
56
|
+
* Description lists
|
57
|
+
***********************************************/
|
58
|
+
|
59
|
+
main dt {
|
60
|
+
margin-bottom: 0; /* override rdoc 6.8 */
|
61
|
+
float: unset; /* override rdoc 6.8 */
|
62
|
+
line-height: 1.5; /* matches `main p` */
|
63
|
+
}
|
64
|
+
|
65
|
+
main dl.note-list dt {
|
66
|
+
margin-right: 1em;
|
67
|
+
float: left;
|
68
|
+
}
|
69
|
+
|
70
|
+
main dl.note-list dt:has(+ dt) {
|
71
|
+
margin-right: 0.25em;
|
72
|
+
}
|
73
|
+
|
74
|
+
main dl.note-list dt:has(+ dt)::after {
|
75
|
+
content: ', ';
|
76
|
+
font-weight: normal;
|
77
|
+
}
|
78
|
+
|
79
|
+
main dd {
|
80
|
+
margin: 0 0 1em 1em;
|
81
|
+
}
|
82
|
+
|
83
|
+
main dd p:first-child {
|
84
|
+
margin-top: 0;
|
24
85
|
}
|
@@ -9,7 +9,7 @@ module Net::IMAP::Authenticators
|
|
9
9
|
"%s.%s is deprecated. Use %s.%s instead." % [
|
10
10
|
Net::IMAP, __method__, Net::IMAP::SASL, __method__
|
11
11
|
],
|
12
|
-
uplevel: 1
|
12
|
+
uplevel: 1, category: :deprecated
|
13
13
|
)
|
14
14
|
Net::IMAP::SASL.add_authenticator(...)
|
15
15
|
end
|
@@ -20,7 +20,7 @@ module Net::IMAP::Authenticators
|
|
20
20
|
"%s.%s is deprecated. Use %s.%s instead." % [
|
21
21
|
Net::IMAP, __method__, Net::IMAP::SASL, __method__
|
22
22
|
],
|
23
|
-
uplevel: 1
|
23
|
+
uplevel: 1, category: :deprecated
|
24
24
|
)
|
25
25
|
Net::IMAP::SASL.authenticator(...)
|
26
26
|
end
|
@@ -3,6 +3,7 @@
|
|
3
3
|
require "date"
|
4
4
|
|
5
5
|
require_relative "errors"
|
6
|
+
require_relative "data_lite"
|
6
7
|
|
7
8
|
module Net
|
8
9
|
class IMAP < Protocol
|
@@ -40,10 +41,10 @@ module Net
|
|
40
41
|
send_number_data(data)
|
41
42
|
when Array
|
42
43
|
send_list_data(data, tag)
|
43
|
-
when Date
|
44
|
-
send_date_data(data)
|
45
44
|
when Time, DateTime
|
46
45
|
send_time_data(data)
|
46
|
+
when Date
|
47
|
+
send_date_data(data)
|
47
48
|
when Symbol
|
48
49
|
send_symbol_data(data)
|
49
50
|
else
|
@@ -119,79 +120,95 @@ module Net
|
|
119
120
|
put_string("\\" + symbol.to_s)
|
120
121
|
end
|
121
122
|
|
122
|
-
|
123
|
+
CommandData = Data.define(:data) do # :nodoc:
|
123
124
|
def send_data(imap, tag)
|
124
|
-
|
125
|
+
raise NoMethodError, "#{self.class} must implement #{__method__}"
|
125
126
|
end
|
126
127
|
|
127
128
|
def validate
|
128
129
|
end
|
129
|
-
|
130
|
-
private
|
131
|
-
|
132
|
-
def initialize(data)
|
133
|
-
@data = data
|
134
|
-
end
|
135
130
|
end
|
136
131
|
|
137
|
-
class
|
132
|
+
class RawData < CommandData # :nodoc:
|
138
133
|
def send_data(imap, tag)
|
139
|
-
imap.__send__(:put_string,
|
140
|
-
end
|
141
|
-
|
142
|
-
def validate
|
143
|
-
end
|
144
|
-
|
145
|
-
private
|
146
|
-
|
147
|
-
def initialize(data)
|
148
|
-
@data = data
|
134
|
+
imap.__send__(:put_string, data)
|
149
135
|
end
|
150
136
|
end
|
151
137
|
|
152
|
-
class
|
138
|
+
class Atom < CommandData # :nodoc:
|
153
139
|
def send_data(imap, tag)
|
154
|
-
imap.__send__(:
|
140
|
+
imap.__send__(:put_string, data)
|
155
141
|
end
|
142
|
+
end
|
156
143
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
private
|
161
|
-
|
162
|
-
def initialize(data)
|
163
|
-
@data = data
|
144
|
+
class QuotedString < CommandData # :nodoc:
|
145
|
+
def send_data(imap, tag)
|
146
|
+
imap.__send__(:send_quoted_string, data)
|
164
147
|
end
|
165
148
|
end
|
166
149
|
|
167
|
-
class Literal # :nodoc:
|
150
|
+
class Literal < CommandData # :nodoc:
|
168
151
|
def send_data(imap, tag)
|
169
|
-
imap.__send__(:send_literal,
|
152
|
+
imap.__send__(:send_literal, data, tag)
|
170
153
|
end
|
154
|
+
end
|
171
155
|
|
172
|
-
|
156
|
+
class PartialRange < CommandData # :nodoc:
|
157
|
+
uint32_max = 2**32 - 1
|
158
|
+
POS_RANGE = 1..uint32_max
|
159
|
+
NEG_RANGE = -uint32_max..-1
|
160
|
+
Positive = ->{ (_1 in Range) and POS_RANGE.cover?(_1) }
|
161
|
+
Negative = ->{ (_1 in Range) and NEG_RANGE.cover?(_1) }
|
162
|
+
|
163
|
+
def initialize(data:)
|
164
|
+
min, max = case data
|
165
|
+
in Range
|
166
|
+
data.minmax.map { Integer _1 }
|
167
|
+
in ResponseParser::Patterns::PARTIAL_RANGE
|
168
|
+
data.split(":").map { Integer _1 }.minmax
|
169
|
+
else
|
170
|
+
raise ArgumentError, "invalid partial range input: %p" % [data]
|
171
|
+
end
|
172
|
+
data = min..max
|
173
|
+
unless data in Positive | Negative
|
174
|
+
raise ArgumentError, "invalid partial-range: %p" % [data]
|
175
|
+
end
|
176
|
+
super
|
177
|
+
rescue TypeError, RangeError
|
178
|
+
raise ArgumentError, "expected range min/max to be Integers"
|
173
179
|
end
|
174
180
|
|
175
|
-
|
181
|
+
def formatted = "%d:%d" % data.minmax
|
176
182
|
|
177
|
-
def
|
178
|
-
|
183
|
+
def send_data(imap, tag)
|
184
|
+
imap.__send__(:put_string, formatted)
|
179
185
|
end
|
180
186
|
end
|
181
187
|
|
182
|
-
|
188
|
+
# *DEPRECATED*. Replaced by SequenceSet.
|
189
|
+
class MessageSet < CommandData # :nodoc:
|
183
190
|
def send_data(imap, tag)
|
184
|
-
imap.__send__(:put_string, format_internal(
|
191
|
+
imap.__send__(:put_string, format_internal(data))
|
185
192
|
end
|
186
193
|
|
187
194
|
def validate
|
188
|
-
validate_internal(
|
195
|
+
validate_internal(data)
|
189
196
|
end
|
190
197
|
|
191
198
|
private
|
192
199
|
|
193
|
-
def initialize(data)
|
194
|
-
|
200
|
+
def initialize(data:)
|
201
|
+
super
|
202
|
+
warn("DEPRECATED: #{MessageSet} should be replaced with #{SequenceSet}.",
|
203
|
+
uplevel: 1, category: :deprecated)
|
204
|
+
begin
|
205
|
+
# to ensure the input works with SequenceSet, too
|
206
|
+
SequenceSet.new(data)
|
207
|
+
rescue
|
208
|
+
warn "MessageSet input is incompatible with SequenceSet: [%s] %s" % [
|
209
|
+
$!.class, $!.message
|
210
|
+
]
|
211
|
+
end
|
195
212
|
end
|
196
213
|
|
197
214
|
def format_internal(data)
|
@@ -235,22 +252,18 @@ module Net
|
|
235
252
|
end
|
236
253
|
end
|
237
254
|
|
238
|
-
class ClientID # :nodoc:
|
255
|
+
class ClientID < CommandData # :nodoc:
|
239
256
|
|
240
257
|
def send_data(imap, tag)
|
241
|
-
imap.__send__(:send_data, format_internal(
|
258
|
+
imap.__send__(:send_data, format_internal(data), tag)
|
242
259
|
end
|
243
260
|
|
244
261
|
def validate
|
245
|
-
validate_internal(
|
262
|
+
validate_internal(data)
|
246
263
|
end
|
247
264
|
|
248
265
|
private
|
249
266
|
|
250
|
-
def initialize(data)
|
251
|
-
@data = data
|
252
|
-
end
|
253
|
-
|
254
267
|
def validate_internal(client_id)
|
255
268
|
client_id.to_h.each do |k,v|
|
256
269
|
unless StringFormatter.valid_string?(k)
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "forwardable"
|
4
|
+
|
5
|
+
module Net
|
6
|
+
class IMAP
|
7
|
+
class Config
|
8
|
+
# >>>
|
9
|
+
# *NOTE:* This module is an internal implementation detail, with no
|
10
|
+
# guarantee of backward compatibility.
|
11
|
+
#
|
12
|
+
# +attr_accessor+ values are stored in a struct rather than ivars, making
|
13
|
+
# it simpler to ensure that all config objects share a single object
|
14
|
+
# shape. This also simplifies iteration over all defined attributes.
|
15
|
+
module AttrAccessors
|
16
|
+
module Macros # :nodoc: internal API
|
17
|
+
def attr_accessor(name) AttrAccessors.attr_accessor(name) end
|
18
|
+
end
|
19
|
+
private_constant :Macros
|
20
|
+
|
21
|
+
def self.included(mod)
|
22
|
+
mod.extend Macros
|
23
|
+
end
|
24
|
+
private_class_method :included
|
25
|
+
|
26
|
+
extend Forwardable
|
27
|
+
|
28
|
+
def self.attr_accessor(name) # :nodoc: internal API
|
29
|
+
name = name.to_sym
|
30
|
+
def_delegators :data, name, :"#{name}="
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.attributes
|
34
|
+
instance_methods.grep(/=\z/).map { _1.to_s.delete_suffix("=").to_sym }
|
35
|
+
end
|
36
|
+
private_class_method :attributes
|
37
|
+
|
38
|
+
def self.struct # :nodoc: internal API
|
39
|
+
unless defined?(self::Struct)
|
40
|
+
const_set :Struct, Struct.new(*attributes)
|
41
|
+
end
|
42
|
+
self::Struct
|
43
|
+
end
|
44
|
+
|
45
|
+
def initialize # :notnew:
|
46
|
+
super()
|
47
|
+
@data = AttrAccessors.struct.new
|
48
|
+
end
|
49
|
+
|
50
|
+
# Freezes the internal attributes struct, in addition to +self+.
|
51
|
+
def freeze
|
52
|
+
data.freeze
|
53
|
+
super
|
54
|
+
end
|
55
|
+
|
56
|
+
protected
|
57
|
+
|
58
|
+
attr_reader :data # :nodoc: internal API
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def initialize_clone(other)
|
63
|
+
super
|
64
|
+
@data = other.data.clone
|
65
|
+
end
|
66
|
+
|
67
|
+
def initialize_dup(other)
|
68
|
+
super
|
69
|
+
@data = other.data.dup
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Net
|
4
|
+
class IMAP
|
5
|
+
class Config
|
6
|
+
# >>>
|
7
|
+
# *NOTE:* The public methods on this module are part of the stable
|
8
|
+
# public API of Net::IMAP::Config. But the module itself is an internal
|
9
|
+
# implementation detail, with no guarantee of backward compatibility.
|
10
|
+
#
|
11
|
+
# +attr_accessor+ methods will delegate to their #parent when the local
|
12
|
+
# value does not contain an override. Inheritance forms a singly linked
|
13
|
+
# list, so lookup will be <tt>O(n)</tt> on the number of ancestors. In
|
14
|
+
# practice, the ancestor chain is not expected to be long. Without
|
15
|
+
# customization, it is only three deep:
|
16
|
+
# >>>
|
17
|
+
# IMAP#config → Config.global → Config.default
|
18
|
+
#
|
19
|
+
# When creating a client with the +config+ keyword, for example to use
|
20
|
+
# the appropriate defaults for an application or a library while still
|
21
|
+
# relying on global for configuration of +debug+ or +logger+, most likely
|
22
|
+
# the ancestor chain is still only four deep:
|
23
|
+
# >>>
|
24
|
+
# IMAP#config → alternate defaults → Config.global → Config.default
|
25
|
+
module AttrInheritance
|
26
|
+
INHERITED = Module.new.freeze
|
27
|
+
private_constant :INHERITED
|
28
|
+
|
29
|
+
module Macros # :nodoc: internal API
|
30
|
+
def attr_accessor(name) super; AttrInheritance.attr_accessor(name) end
|
31
|
+
end
|
32
|
+
private_constant :Macros
|
33
|
+
|
34
|
+
def self.included(mod)
|
35
|
+
mod.extend Macros
|
36
|
+
end
|
37
|
+
private_class_method :included
|
38
|
+
|
39
|
+
def self.attr_accessor(name) # :nodoc: internal API
|
40
|
+
module_eval <<~RUBY, __FILE__, __LINE__ + 1
|
41
|
+
def #{name}; (val = super) == INHERITED ? parent&.#{name} : val end
|
42
|
+
RUBY
|
43
|
+
end
|
44
|
+
|
45
|
+
# The parent Config object
|
46
|
+
attr_reader :parent
|
47
|
+
|
48
|
+
def initialize(parent = nil) # :notnew:
|
49
|
+
super()
|
50
|
+
@parent = Config[parent]
|
51
|
+
reset
|
52
|
+
end
|
53
|
+
|
54
|
+
# Creates a new config, which inherits from +self+.
|
55
|
+
def new(**attrs) self.class.new(self, **attrs) end
|
56
|
+
|
57
|
+
# Returns +true+ if +attr+ is inherited from #parent and not overridden
|
58
|
+
# by this config.
|
59
|
+
def inherited?(attr) data[attr] == INHERITED end
|
60
|
+
|
61
|
+
# :call-seq:
|
62
|
+
# reset -> self
|
63
|
+
# reset(attr) -> attribute value
|
64
|
+
#
|
65
|
+
# Resets an +attr+ to inherit from the #parent config.
|
66
|
+
#
|
67
|
+
# When +attr+ is nil or not given, all attributes are reset.
|
68
|
+
def reset(attr = nil)
|
69
|
+
if attr.nil?
|
70
|
+
data.members.each do |attr| data[attr] = INHERITED end
|
71
|
+
self
|
72
|
+
elsif inherited?(attr)
|
73
|
+
nil
|
74
|
+
else
|
75
|
+
old, data[attr] = data[attr], INHERITED
|
76
|
+
old
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def initialize_copy(other)
|
83
|
+
super
|
84
|
+
@parent ||= other # only default has nil parent
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Net
|
4
|
+
class IMAP
|
5
|
+
class Config
|
6
|
+
# >>>
|
7
|
+
# *NOTE:* This module is an internal implementation detail, with no
|
8
|
+
# guarantee of backward compatibility.
|
9
|
+
#
|
10
|
+
# Adds a +type+ keyword parameter to +attr_accessor+, to enforce that
|
11
|
+
# config attributes have valid types, for example: boolean, numeric,
|
12
|
+
# enumeration, non-nullable, etc.
|
13
|
+
module AttrTypeCoercion
|
14
|
+
# :stopdoc: internal APIs only
|
15
|
+
|
16
|
+
module Macros # :nodoc: internal API
|
17
|
+
def attr_accessor(attr, type: nil)
|
18
|
+
super(attr)
|
19
|
+
AttrTypeCoercion.attr_accessor(attr, type: type)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
private_constant :Macros
|
23
|
+
|
24
|
+
def self.included(mod)
|
25
|
+
mod.extend Macros
|
26
|
+
end
|
27
|
+
private_class_method :included
|
28
|
+
|
29
|
+
def self.attr_accessor(attr, type: nil)
|
30
|
+
return unless type
|
31
|
+
if :boolean == type then boolean attr
|
32
|
+
elsif Integer == type then integer attr
|
33
|
+
elsif Array === type then enum attr, type
|
34
|
+
else raise ArgumentError, "unknown type coercion %p" % [type]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.boolean(attr)
|
39
|
+
define_method :"#{attr}=" do |val| super !!val end
|
40
|
+
define_method :"#{attr}?" do send attr end
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.integer(attr)
|
44
|
+
define_method :"#{attr}=" do |val| super Integer val end
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.enum(attr, enum)
|
48
|
+
enum = enum.dup.freeze
|
49
|
+
expected = -"one of #{enum.map(&:inspect).join(", ")}"
|
50
|
+
define_method :"#{attr}=" do |val|
|
51
|
+
unless enum.include?(val)
|
52
|
+
raise ArgumentError, "expected %s, got %p" % [expected, val]
|
53
|
+
end
|
54
|
+
super val
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|