net-imap 0.4.24 → 0.5.14
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 +4 -4
- data/Gemfile +10 -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 +73 -78
- data/lib/net/imap/config/attr_type_coercion.rb +22 -10
- data/lib/net/imap/config/attr_version_defaults.rb +93 -0
- data/lib/net/imap/config.rb +70 -94
- data/lib/net/imap/connection_state.rb +48 -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 +6 -3
- data/lib/net/imap/errors.rb +6 -0
- data/lib/net/imap/esearch_result.rb +219 -0
- data/lib/net/imap/fetch_data.rb +126 -47
- data/lib/net/imap/flags.rb +1 -1
- data/lib/net/imap/response_data.rb +120 -186
- data/lib/net/imap/response_parser/parser_utils.rb +5 -0
- data/lib/net/imap/response_parser.rb +155 -21
- data/lib/net/imap/response_reader.rb +9 -12
- 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 +10 -10
- data/lib/net/imap/sasl.rb +7 -4
- data/lib/net/imap/sasl_adapter.rb +0 -1
- data/lib/net/imap/search_result.rb +4 -5
- data/lib/net/imap/sequence_set.rb +529 -154
- data/lib/net/imap/stringprep/nameprep.rb +1 -1
- data/lib/net/imap/stringprep/trace.rb +4 -4
- data/lib/net/imap/uidplus_data.rb +2 -84
- data/lib/net/imap/vanished_data.rb +65 -0
- data/lib/net/imap.rb +996 -305
- data/net-imap.gemspec +1 -1
- data/rakelib/rfcs.rake +2 -0
- data/rakelib/string_prep_tables_generator.rb +6 -2
- metadata +7 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 33bccbb75eba778cb42fc5340afc2f10a899ca671123e8b538a39acbdf16bd1b
|
|
4
|
+
data.tar.gz: c4252164f38a0f36b827fb32247500e95293880e2f8026f4b7ed04926614df41
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e682041f5c1f0e071578c0910f3eace9438064741cce16b148870c73137fd38926154269907f4948dec365e83b9115facff5ec21ac23072270ef39bab64cea10
|
|
7
|
+
data.tar.gz: 8a04cac2ad54cd0bc4b2e2f5855bac16f571c3ab6aa0183caa1427ad7c420f8c3920e74b85871c22454dbd827c7772b24cfe96bcaf262222664c05f7483f5dbd
|
data/Gemfile
CHANGED
|
@@ -8,9 +8,18 @@ gemspec
|
|
|
8
8
|
gem "strscan"
|
|
9
9
|
gem "base64"
|
|
10
10
|
|
|
11
|
+
gem "irb"
|
|
11
12
|
gem "rake"
|
|
12
13
|
gem "rdoc"
|
|
13
14
|
gem "test-unit"
|
|
14
15
|
gem "test-unit-ruby-core", git: "https://github.com/ruby/test-unit-ruby-core"
|
|
15
16
|
|
|
16
|
-
gem "benchmark
|
|
17
|
+
gem "benchmark", require: false
|
|
18
|
+
gem "benchmark-driver", require: false
|
|
19
|
+
gem "vernier", require: false, platform: :mri
|
|
20
|
+
|
|
21
|
+
group :test do
|
|
22
|
+
gem "simplecov", require: false, platforms: %i[mri windows]
|
|
23
|
+
gem "simplecov-html", require: false, platforms: %i[mri windows]
|
|
24
|
+
gem "simplecov-json", require: false, platforms: %i[mri windows]
|
|
25
|
+
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,9 @@
|
|
|
3
3
|
require "date"
|
|
4
4
|
|
|
5
5
|
require_relative "errors"
|
|
6
|
+
require_relative "data_lite"
|
|
7
|
+
|
|
8
|
+
# :enddoc:
|
|
6
9
|
|
|
7
10
|
module Net
|
|
8
11
|
class IMAP < Protocol
|
|
@@ -78,7 +81,7 @@ module Net
|
|
|
78
81
|
put_string('"' + str.gsub(/["\\]/, "\\\\\\&") + '"')
|
|
79
82
|
end
|
|
80
83
|
|
|
81
|
-
def send_binary_literal(*a, **kw) send_literal(*a, **kw, binary: true) end
|
|
84
|
+
def send_binary_literal(*a, **kw); send_literal(*a, **kw, binary: true) end
|
|
82
85
|
|
|
83
86
|
# `non_sync` is an optional tri-state flag:
|
|
84
87
|
# * `true` -> Force non-synchronizing `LITERAL+`/`LITERAL-` behavior.
|
|
@@ -130,27 +133,7 @@ module Net
|
|
|
130
133
|
def send_date_data(date) put_string Net::IMAP.encode_date(date) end
|
|
131
134
|
def send_time_data(time) put_string Net::IMAP.encode_time(time) end
|
|
132
135
|
|
|
133
|
-
|
|
134
|
-
class CommandData # :nodoc:
|
|
135
|
-
class << self
|
|
136
|
-
def new(arg = nil, data: arg) super(data: data) end
|
|
137
|
-
alias :[] :new
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
def initialize(data:)
|
|
141
|
-
@data = data
|
|
142
|
-
freeze
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
attr_reader :data
|
|
146
|
-
|
|
147
|
-
def to_h(&block) block ? to_h.to_h(&block) : { data: data } end
|
|
148
|
-
def ==(other) self.class === other && to_h == other.to_h end
|
|
149
|
-
def eql?(other) self.class === other && to_h.eql?(other.to_h) end
|
|
150
|
-
|
|
151
|
-
# following class definition goes beyond the basic Data.define(:data)
|
|
152
|
-
##
|
|
153
|
-
|
|
136
|
+
CommandData = Data.define(:data) do # :nodoc:
|
|
154
137
|
def self.validate(...)
|
|
155
138
|
data = new(...)
|
|
156
139
|
data.validate
|
|
@@ -179,7 +162,7 @@ module Net
|
|
|
179
162
|
class RawText < CommandData # :nodoc:
|
|
180
163
|
def initialize(data:)
|
|
181
164
|
data = String(data.to_str)
|
|
182
|
-
data = if
|
|
165
|
+
data = if data.encoding in Encoding::ASCII | Encoding::UTF_8
|
|
183
166
|
-data
|
|
184
167
|
elsif data.ascii_only?
|
|
185
168
|
-(data.dup.force_encoding("ASCII"))
|
|
@@ -200,9 +183,9 @@ module Net
|
|
|
200
183
|
end
|
|
201
184
|
end
|
|
202
185
|
|
|
203
|
-
def ascii_only
|
|
186
|
+
def ascii_only? = data.ascii_only?
|
|
204
187
|
|
|
205
|
-
def send_data(imap, tag) imap.__send__(:put_string, data)
|
|
188
|
+
def send_data(imap, tag) = imap.__send__(:put_string, data)
|
|
206
189
|
end
|
|
207
190
|
|
|
208
191
|
class RawData < CommandData # :nodoc:
|
|
@@ -212,11 +195,10 @@ module Net
|
|
|
212
195
|
validate
|
|
213
196
|
end
|
|
214
197
|
|
|
215
|
-
def send_data(imap, tag) data.each do _1.send_data(imap, tag) end
|
|
198
|
+
def send_data(imap, tag) = data.each do _1.send_data(imap, tag) end
|
|
216
199
|
|
|
217
200
|
def validate
|
|
218
|
-
return unless
|
|
219
|
-
text = data.last.data
|
|
201
|
+
return unless data.last in RawText(data: text)
|
|
220
202
|
if text.rindex(/~?\{[1-9]\d*\+?\}\z/n)
|
|
221
203
|
raise DataFormatError, "RawData cannot end with literal continuation"
|
|
222
204
|
end
|
|
@@ -231,10 +213,7 @@ module Net
|
|
|
231
213
|
text, binary, bytesize, non_sync, data = $`, !!$1, $2, !!$3, $'
|
|
232
214
|
bytesize = Integer bytesize, 10
|
|
233
215
|
parts << RawText[text] unless text.empty?
|
|
234
|
-
parts << extract_literal(data,
|
|
235
|
-
binary: binary,
|
|
236
|
-
bytesize: bytesize,
|
|
237
|
-
non_sync: non_sync)
|
|
216
|
+
parts << extract_literal(data, binary:, bytesize:, non_sync:)
|
|
238
217
|
data[0, bytesize] = ""
|
|
239
218
|
end
|
|
240
219
|
parts << RawText[data] unless data.empty?
|
|
@@ -247,7 +226,7 @@ module Net
|
|
|
247
226
|
"expected: %s, remaining: %s" % [bytesize, data.bytesize]
|
|
248
227
|
end
|
|
249
228
|
literal = data.byteslice(0, bytesize)
|
|
250
|
-
(binary ? Literal8 : Literal).new(data: literal, non_sync:
|
|
229
|
+
(binary ? Literal8 : Literal).new(data: literal, non_sync:)
|
|
251
230
|
end
|
|
252
231
|
end
|
|
253
232
|
|
|
@@ -275,47 +254,24 @@ module Net
|
|
|
275
254
|
end
|
|
276
255
|
end
|
|
277
256
|
|
|
278
|
-
class QuotedString # :nodoc:
|
|
257
|
+
class QuotedString < CommandData # :nodoc:
|
|
279
258
|
def send_data(imap, tag)
|
|
280
|
-
imap.__send__(:send_quoted_string,
|
|
281
|
-
end
|
|
282
|
-
|
|
283
|
-
def validate
|
|
284
|
-
end
|
|
285
|
-
|
|
286
|
-
private
|
|
287
|
-
|
|
288
|
-
def initialize(data)
|
|
289
|
-
@data = data
|
|
259
|
+
imap.__send__(:send_quoted_string, data)
|
|
290
260
|
end
|
|
291
261
|
end
|
|
292
262
|
|
|
293
|
-
class Literal # :nodoc:
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
alias :[] :new
|
|
263
|
+
class Literal < Data.define(:data, :non_sync) # :nodoc:
|
|
264
|
+
def self.validate(...)
|
|
265
|
+
data = new(...)
|
|
266
|
+
data.validate
|
|
267
|
+
data
|
|
299
268
|
end
|
|
300
269
|
|
|
301
|
-
attr_reader :data, :non_sync
|
|
302
|
-
|
|
303
|
-
def to_h(&block) block ? to_h.to_h(&block) : { data: data, non_sync: non_sync } end
|
|
304
|
-
def ==(other) self.class === other && to_h == other.to_h end
|
|
305
|
-
def eql?(other) self.class === other && to_h.eql?(other.to_h) end
|
|
306
|
-
|
|
307
270
|
def initialize(data:, non_sync: nil)
|
|
308
271
|
data = -String(data.to_str).b or
|
|
309
272
|
raise DataFormatError, "#{self.class} expects string input"
|
|
310
|
-
|
|
273
|
+
super
|
|
311
274
|
validate
|
|
312
|
-
freeze
|
|
313
|
-
end
|
|
314
|
-
|
|
315
|
-
def self.validate(...)
|
|
316
|
-
data = new(...)
|
|
317
|
-
data.validate
|
|
318
|
-
data
|
|
319
275
|
end
|
|
320
276
|
|
|
321
277
|
def bytesize; data.bytesize end
|
|
@@ -328,7 +284,7 @@ module Net
|
|
|
328
284
|
end
|
|
329
285
|
|
|
330
286
|
def send_data(imap, tag)
|
|
331
|
-
imap.__send__(:send_literal, data, tag, non_sync:
|
|
287
|
+
imap.__send__(:send_literal, data, tag, non_sync:)
|
|
332
288
|
end
|
|
333
289
|
end
|
|
334
290
|
|
|
@@ -336,23 +292,66 @@ module Net
|
|
|
336
292
|
def validate; nil end # all bytes are okay
|
|
337
293
|
|
|
338
294
|
def send_data(imap, tag)
|
|
339
|
-
imap.__send__(:send_binary_literal, data, tag, non_sync:
|
|
295
|
+
imap.__send__(:send_binary_literal, data, tag, non_sync:)
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
class PartialRange < CommandData # :nodoc:
|
|
300
|
+
uint32_max = 2**32 - 1
|
|
301
|
+
POS_RANGE = 1..uint32_max
|
|
302
|
+
NEG_RANGE = -uint32_max..-1
|
|
303
|
+
Positive = ->{ (_1 in Range) and POS_RANGE.cover?(_1) }
|
|
304
|
+
Negative = ->{ (_1 in Range) and NEG_RANGE.cover?(_1) }
|
|
305
|
+
|
|
306
|
+
def initialize(data:)
|
|
307
|
+
min, max = case data
|
|
308
|
+
in Range
|
|
309
|
+
data.minmax.map { Integer _1 }
|
|
310
|
+
in ResponseParser::Patterns::PARTIAL_RANGE
|
|
311
|
+
data.split(":").map { Integer _1 }.minmax
|
|
312
|
+
else
|
|
313
|
+
raise ArgumentError, "invalid partial range input: %p" % [data]
|
|
314
|
+
end
|
|
315
|
+
data = min..max
|
|
316
|
+
unless data in Positive | Negative
|
|
317
|
+
raise ArgumentError, "invalid partial-range: %p" % [data]
|
|
318
|
+
end
|
|
319
|
+
super
|
|
320
|
+
rescue TypeError, RangeError
|
|
321
|
+
raise ArgumentError, "expected range min/max to be Integers"
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
def formatted = "%d:%d" % data.minmax
|
|
325
|
+
|
|
326
|
+
def send_data(imap, tag)
|
|
327
|
+
imap.__send__(:put_string, formatted)
|
|
340
328
|
end
|
|
341
329
|
end
|
|
342
330
|
|
|
343
|
-
|
|
331
|
+
# *DEPRECATED*. Replaced by SequenceSet.
|
|
332
|
+
class MessageSet < CommandData # :nodoc:
|
|
344
333
|
def send_data(imap, tag)
|
|
345
|
-
imap.__send__(:put_string, format_internal(
|
|
334
|
+
imap.__send__(:put_string, format_internal(data))
|
|
346
335
|
end
|
|
347
336
|
|
|
348
337
|
def validate
|
|
349
|
-
validate_internal(
|
|
338
|
+
validate_internal(data)
|
|
350
339
|
end
|
|
351
340
|
|
|
352
341
|
private
|
|
353
342
|
|
|
354
|
-
def initialize(data)
|
|
355
|
-
|
|
343
|
+
def initialize(data:)
|
|
344
|
+
super
|
|
345
|
+
warn("DEPRECATED: #{MessageSet} should be replaced with #{SequenceSet}.",
|
|
346
|
+
uplevel: 1, category: :deprecated)
|
|
347
|
+
begin
|
|
348
|
+
# to ensure the input works with SequenceSet, too
|
|
349
|
+
SequenceSet.new(data)
|
|
350
|
+
rescue
|
|
351
|
+
warn "MessageSet input is incompatible with SequenceSet: [%s] %s" % [
|
|
352
|
+
$!.class, $!.message
|
|
353
|
+
]
|
|
354
|
+
end
|
|
356
355
|
end
|
|
357
356
|
|
|
358
357
|
def format_internal(data)
|
|
@@ -396,22 +395,18 @@ module Net
|
|
|
396
395
|
end
|
|
397
396
|
end
|
|
398
397
|
|
|
399
|
-
class ClientID # :nodoc:
|
|
398
|
+
class ClientID < CommandData # :nodoc:
|
|
400
399
|
|
|
401
400
|
def send_data(imap, tag)
|
|
402
|
-
imap.__send__(:send_data, format_internal(
|
|
401
|
+
imap.__send__(:send_data, format_internal(data), tag)
|
|
403
402
|
end
|
|
404
403
|
|
|
405
404
|
def validate
|
|
406
|
-
validate_internal(
|
|
405
|
+
validate_internal(data)
|
|
407
406
|
end
|
|
408
407
|
|
|
409
408
|
private
|
|
410
409
|
|
|
411
|
-
def initialize(data)
|
|
412
|
-
@data = data
|
|
413
|
-
end
|
|
414
|
-
|
|
415
410
|
def validate_internal(client_id)
|
|
416
411
|
client_id.to_h.each do |k,v|
|
|
417
412
|
unless StringFormatter.valid_string?(k)
|
|
@@ -19,7 +19,7 @@ module Net
|
|
|
19
19
|
AttrTypeCoercion.attr_accessor(attr, type: type)
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
module_function def Integer
|
|
22
|
+
module_function def Integer? = NilOrInteger
|
|
23
23
|
end
|
|
24
24
|
private_constant :Macros
|
|
25
25
|
|
|
@@ -28,14 +28,26 @@ module Net
|
|
|
28
28
|
end
|
|
29
29
|
private_class_method :included
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
if defined?(Ractor.shareable_proc)
|
|
32
|
+
def self.safe(&b)
|
|
33
|
+
case obj = b.call
|
|
34
|
+
when Proc
|
|
35
|
+
Ractor.shareable_proc(&obj)
|
|
36
|
+
else
|
|
37
|
+
Ractor.make_shareable obj
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
elsif defined?(Ractor.make_shareable)
|
|
41
|
+
def self.safe(&b)
|
|
42
|
+
obj = nil.instance_eval(&b).freeze
|
|
43
|
+
Ractor.make_shareable obj
|
|
44
|
+
end
|
|
45
|
+
else
|
|
46
|
+
def self.safe(&b) nil.instance_eval(&b).freeze end
|
|
47
|
+
end
|
|
33
48
|
private_class_method :safe
|
|
34
49
|
|
|
35
|
-
Types = Hash.new do |h, type|
|
|
36
|
-
type.nil? || Proc === type or raise TypeError, "type not nil or Proc"
|
|
37
|
-
safe{type}
|
|
38
|
-
end
|
|
50
|
+
Types = Hash.new do |h, type| type => Proc | nil; safe{type} end
|
|
39
51
|
Types[:boolean] = Boolean = safe{-> {!!_1}}
|
|
40
52
|
Types[Integer] = safe{->{Integer(_1)}}
|
|
41
53
|
|
|
@@ -48,10 +60,10 @@ module Net
|
|
|
48
60
|
NilOrInteger = safe{->val { Integer val unless val.nil? }}
|
|
49
61
|
|
|
50
62
|
Enum = ->(*enum) {
|
|
51
|
-
|
|
52
|
-
expected = -"one of #{
|
|
63
|
+
safe_enum = safe{enum}
|
|
64
|
+
expected = -"one of #{safe_enum.map(&:inspect).join(", ")}"
|
|
53
65
|
safe{->val {
|
|
54
|
-
return val if
|
|
66
|
+
return val if safe_enum.include?(val)
|
|
55
67
|
raise ArgumentError, "expected %s, got %p" % [expected, val]
|
|
56
68
|
}}
|
|
57
69
|
}
|
|
@@ -0,0 +1,93 @@
|
|
|
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
|
+
# Adds a +defaults+ parameter to +attr_accessor+, which is used to compile
|
|
13
|
+
# Config.version_defaults.
|
|
14
|
+
module AttrVersionDefaults
|
|
15
|
+
# The <tt>x.y</tt> part of Net::IMAP::VERSION, as a Rational number.
|
|
16
|
+
CURRENT_VERSION = VERSION.to_r
|
|
17
|
+
|
|
18
|
+
# The config version used for <tt>Config[:next]</tt>.
|
|
19
|
+
NEXT_VERSION = CURRENT_VERSION + 0.1r
|
|
20
|
+
|
|
21
|
+
# The config version used for <tt>Config[:future]</tt>.
|
|
22
|
+
FUTURE_VERSION = 1.0r
|
|
23
|
+
|
|
24
|
+
VERSIONS = ((0.0r..FUTURE_VERSION) % 0.1r).to_a.freeze
|
|
25
|
+
|
|
26
|
+
# See Config.version_defaults.
|
|
27
|
+
singleton_class.attr_reader :version_defaults
|
|
28
|
+
|
|
29
|
+
@version_defaults = Hash.new {|h, k|
|
|
30
|
+
# NOTE: String responds to both so the order is significant.
|
|
31
|
+
# And ignore non-numeric conversion to zero, because: "wat!?".to_r == 0
|
|
32
|
+
(h.fetch(k.to_r, nil) || h.fetch(k.to_f, nil) if k.is_a?(Numeric)) ||
|
|
33
|
+
(h.fetch(k.to_sym, nil) if k.respond_to?(:to_sym)) ||
|
|
34
|
+
(h.fetch(k.to_r, nil) if k.respond_to?(:to_r) && k.to_r != 0r) ||
|
|
35
|
+
(h.fetch(k.to_f, nil) if k.respond_to?(:to_f) && k.to_f != 0.0)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
# :stopdoc: internal APIs only
|
|
39
|
+
|
|
40
|
+
def attr_accessor(name, defaults: nil, default: (unset = true), **kw)
|
|
41
|
+
unless unset
|
|
42
|
+
version = DEFAULT_TO_INHERIT.include?(name) ? nil : 0.0r
|
|
43
|
+
defaults = { version => default }
|
|
44
|
+
end
|
|
45
|
+
defaults&.each_pair do |version, default|
|
|
46
|
+
AttrVersionDefaults.version_defaults[version] ||= {}
|
|
47
|
+
AttrVersionDefaults.version_defaults[version][name] = default
|
|
48
|
+
end
|
|
49
|
+
super(name, **kw)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def self.compile_default!
|
|
53
|
+
raise "Config.default already compiled" if Config.default
|
|
54
|
+
default = VERSIONS.select { _1 <= CURRENT_VERSION }
|
|
55
|
+
.filter_map { version_defaults[_1] }
|
|
56
|
+
.prepend(version_defaults.delete(nil))
|
|
57
|
+
.inject(&:merge)
|
|
58
|
+
Config.new(**default).freeze
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def self.compile_version_defaults!
|
|
62
|
+
# Temporarily assign Config.default, enabling #load_defaults(:default)
|
|
63
|
+
version_defaults[:default] = Config.default
|
|
64
|
+
# Use #load_defaults so some attributes are inherited from global.
|
|
65
|
+
version_defaults[:default] = Config.new.load_defaults(:default).freeze
|
|
66
|
+
version_defaults[0.0r] = Config[version_defaults.fetch(0.0r)]
|
|
67
|
+
|
|
68
|
+
VERSIONS.each_cons(2) do |prior, version|
|
|
69
|
+
updates = version_defaults[version]
|
|
70
|
+
version_defaults[version] = version_defaults[prior]
|
|
71
|
+
.then { updates ? _1.dup.update(**updates).freeze : _1 }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Safe conversions one way only:
|
|
75
|
+
# 0.6r.to_f == 0.6 # => true
|
|
76
|
+
# 0.6 .to_r == 0.6r # => false
|
|
77
|
+
version_defaults.to_a.each do |k, v|
|
|
78
|
+
next unless k in Rational
|
|
79
|
+
version_defaults[k.to_f] = v
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
version_defaults[:original] = Config[0.0r]
|
|
83
|
+
version_defaults[:current] = Config[CURRENT_VERSION]
|
|
84
|
+
version_defaults[:next] = Config[NEXT_VERSION]
|
|
85
|
+
version_defaults[:future] = Config[FUTURE_VERSION]
|
|
86
|
+
|
|
87
|
+
version_defaults.freeze
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|