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.

Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +7 -1
  3. data/README.md +10 -4
  4. data/docs/styles.css +75 -14
  5. data/lib/net/imap/authenticators.rb +2 -2
  6. data/lib/net/imap/command_data.rb +61 -48
  7. data/lib/net/imap/config/attr_accessors.rb +75 -0
  8. data/lib/net/imap/config/attr_inheritance.rb +90 -0
  9. data/lib/net/imap/config/attr_type_coercion.rb +61 -0
  10. data/lib/net/imap/config.rb +402 -0
  11. data/lib/net/imap/data_encoding.rb +3 -3
  12. data/lib/net/imap/data_lite.rb +226 -0
  13. data/lib/net/imap/deprecated_client_options.rb +8 -5
  14. data/lib/net/imap/errors.rb +6 -0
  15. data/lib/net/imap/esearch_result.rb +180 -0
  16. data/lib/net/imap/fetch_data.rb +126 -47
  17. data/lib/net/imap/response_data.rb +126 -193
  18. data/lib/net/imap/response_parser/parser_utils.rb +11 -6
  19. data/lib/net/imap/response_parser.rb +159 -21
  20. data/lib/net/imap/sasl/anonymous_authenticator.rb +3 -3
  21. data/lib/net/imap/sasl/authentication_exchange.rb +52 -20
  22. data/lib/net/imap/sasl/authenticators.rb +8 -4
  23. data/lib/net/imap/sasl/client_adapter.rb +77 -26
  24. data/lib/net/imap/sasl/cram_md5_authenticator.rb +4 -4
  25. data/lib/net/imap/sasl/digest_md5_authenticator.rb +218 -56
  26. data/lib/net/imap/sasl/external_authenticator.rb +2 -2
  27. data/lib/net/imap/sasl/gs2_header.rb +7 -7
  28. data/lib/net/imap/sasl/login_authenticator.rb +4 -3
  29. data/lib/net/imap/sasl/oauthbearer_authenticator.rb +6 -6
  30. data/lib/net/imap/sasl/plain_authenticator.rb +7 -7
  31. data/lib/net/imap/sasl/protocol_adapters.rb +60 -4
  32. data/lib/net/imap/sasl/scram_authenticator.rb +8 -8
  33. data/lib/net/imap/sasl.rb +7 -4
  34. data/lib/net/imap/sasl_adapter.rb +0 -1
  35. data/lib/net/imap/search_result.rb +2 -2
  36. data/lib/net/imap/sequence_set.rb +28 -24
  37. data/lib/net/imap/stringprep/nameprep.rb +1 -1
  38. data/lib/net/imap/stringprep/trace.rb +4 -4
  39. data/lib/net/imap/vanished_data.rb +56 -0
  40. data/lib/net/imap.rb +1001 -319
  41. data/net-imap.gemspec +3 -3
  42. data/rakelib/rfcs.rake +2 -0
  43. data/rakelib/string_prep_tables_generator.rb +2 -0
  44. metadata +11 -10
  45. data/.github/dependabot.yml +0 -6
  46. data/.github/workflows/pages.yml +0 -46
  47. data/.github/workflows/push_gem.yml +0 -48
  48. data/.github/workflows/test.yml +0 -31
  49. data/.gitignore +0 -12
  50. data/.mailmap +0 -13
@@ -0,0 +1,402 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "config/attr_accessors"
4
+ require_relative "config/attr_inheritance"
5
+ require_relative "config/attr_type_coercion"
6
+
7
+ module Net
8
+ class IMAP
9
+
10
+ # Net::IMAP::Config <em>(available since +v0.4.13+)</em> stores
11
+ # configuration options for Net::IMAP clients. The global configuration can
12
+ # be seen at either Net::IMAP.config or Net::IMAP::Config.global, and the
13
+ # client-specific configuration can be seen at Net::IMAP#config.
14
+ #
15
+ # When creating a new client, all unhandled keyword arguments to
16
+ # Net::IMAP.new are delegated to Config.new. Every client has its own
17
+ # config.
18
+ #
19
+ # debug_client = Net::IMAP.new(hostname, debug: true)
20
+ # quiet_client = Net::IMAP.new(hostname, debug: false)
21
+ # debug_client.config.debug? # => true
22
+ # quiet_client.config.debug? # => false
23
+ #
24
+ # == Inheritance
25
+ #
26
+ # Configs have a parent[rdoc-ref:Config::AttrInheritance#parent] config, and
27
+ # any attributes which have not been set locally will inherit the parent's
28
+ # value. Every client creates its own specific config. By default, client
29
+ # configs inherit from Config.global.
30
+ #
31
+ # plain_client = Net::IMAP.new(hostname)
32
+ # debug_client = Net::IMAP.new(hostname, debug: true)
33
+ # quiet_client = Net::IMAP.new(hostname, debug: false)
34
+ #
35
+ # plain_client.config.inherited?(:debug) # => true
36
+ # debug_client.config.inherited?(:debug) # => false
37
+ # quiet_client.config.inherited?(:debug) # => false
38
+ #
39
+ # plain_client.config.debug? # => false
40
+ # debug_client.config.debug? # => true
41
+ # quiet_client.config.debug? # => false
42
+ #
43
+ # # Net::IMAP.debug is delegated to Net::IMAP::Config.global.debug
44
+ # Net::IMAP.debug = true
45
+ # plain_client.config.debug? # => true
46
+ # debug_client.config.debug? # => true
47
+ # quiet_client.config.debug? # => false
48
+ #
49
+ # Net::IMAP.debug = false
50
+ # plain_client.config.debug = true
51
+ # plain_client.config.inherited?(:debug) # => false
52
+ # plain_client.config.debug? # => true
53
+ # plain_client.config.reset(:debug)
54
+ # plain_client.config.inherited?(:debug) # => true
55
+ # plain_client.config.debug? # => false
56
+ #
57
+ # == Versioned defaults
58
+ #
59
+ # The effective default configuration for a specific +x.y+ version of
60
+ # +net-imap+ can be loaded with the +config+ keyword argument to
61
+ # Net::IMAP.new. Requesting default configurations for previous versions
62
+ # enables extra backward compatibility with those versions:
63
+ #
64
+ # client = Net::IMAP.new(hostname, config: 0.3)
65
+ # client.config.sasl_ir # => false
66
+ # client.config.responses_without_block # => :silence_deprecation_warning
67
+ #
68
+ # client = Net::IMAP.new(hostname, config: 0.4)
69
+ # client.config.sasl_ir # => true
70
+ # client.config.responses_without_block # => :silence_deprecation_warning
71
+ #
72
+ # client = Net::IMAP.new(hostname, config: 0.5)
73
+ # client.config.sasl_ir # => true
74
+ # client.config.responses_without_block # => :warn
75
+ #
76
+ # client = Net::IMAP.new(hostname, config: :future)
77
+ # client.config.sasl_ir # => true
78
+ # client.config.responses_without_block # => :frozen_dup
79
+ #
80
+ # The versioned default configs inherit certain specific config options from
81
+ # Config.global, for example #debug:
82
+ #
83
+ # client = Net::IMAP.new(hostname, config: 0.4)
84
+ # Net::IMAP.debug = false
85
+ # client.config.debug? # => false
86
+ #
87
+ # Net::IMAP.debug = true
88
+ # client.config.debug? # => true
89
+ #
90
+ # Use #load_defaults to globally behave like a specific version:
91
+ # client = Net::IMAP.new(hostname)
92
+ # client.config.sasl_ir # => true
93
+ # Net::IMAP.config.load_defaults 0.3
94
+ # client.config.sasl_ir # => false
95
+ #
96
+ # === Named defaults
97
+ # In addition to +x.y+ version numbers, the following aliases are supported:
98
+ #
99
+ # [+:default+]
100
+ # An alias for +:current+.
101
+ #
102
+ # >>>
103
+ # *NOTE*: This is _not_ the same as Config.default. It inherits some
104
+ # attributes from Config.global, for example: #debug.
105
+ # [+:current+]
106
+ # An alias for the current +x.y+ version's defaults.
107
+ # [+:next+]
108
+ # The _planned_ config for the next +x.y+ version.
109
+ # [+:future+]
110
+ # The _planned_ eventual config for some future +x.y+ version.
111
+ #
112
+ # For example, to disable all currently deprecated behavior:
113
+ # client = Net::IMAP.new(hostname, config: :future)
114
+ # client.config.response_without_args # => :frozen_dup
115
+ # client.responses.frozen? # => true
116
+ # client.responses.values.all?(&:frozen?) # => true
117
+ #
118
+ # == Thread Safety
119
+ #
120
+ # *NOTE:* Updates to config objects are not synchronized for thread-safety.
121
+ #
122
+ class Config
123
+ # Array of attribute names that are _not_ loaded by #load_defaults.
124
+ DEFAULT_TO_INHERIT = %i[debug].freeze
125
+ private_constant :DEFAULT_TO_INHERIT
126
+
127
+ # The default config, which is hardcoded and frozen.
128
+ def self.default; @default end
129
+
130
+ # The global config object. Also available from Net::IMAP.config.
131
+ def self.global; @global if defined?(@global) end
132
+
133
+ # A hash of hard-coded configurations, indexed by version number or name.
134
+ def self.version_defaults; @version_defaults end
135
+ @version_defaults = {}
136
+
137
+ # :call-seq:
138
+ # Net::IMAP::Config[number] -> versioned config
139
+ # Net::IMAP::Config[symbol] -> named config
140
+ # Net::IMAP::Config[hash] -> new frozen config
141
+ # Net::IMAP::Config[config] -> same config
142
+ #
143
+ # Given a version number, returns the default configuration for the target
144
+ # version. See Config@Versioned+defaults.
145
+ #
146
+ # Given a version name, returns the default configuration for the target
147
+ # version. See Config@Named+defaults.
148
+ #
149
+ # Given a Hash, creates a new _frozen_ config which inherits from
150
+ # Config.global. Use Config.new for an unfrozen config.
151
+ #
152
+ # Given a config, returns that same config.
153
+ def self.[](config)
154
+ if config.is_a?(Config) then config
155
+ elsif config.nil? && global.nil? then nil
156
+ elsif config.respond_to?(:to_hash) then new(global, **config).freeze
157
+ else
158
+ version_defaults.fetch(config) do
159
+ case config
160
+ when Numeric
161
+ raise RangeError, "unknown config version: %p" % [config]
162
+ when Symbol
163
+ raise KeyError, "unknown config name: %p" % [config]
164
+ else
165
+ raise TypeError, "no implicit conversion of %s to %s" % [
166
+ config.class, Config
167
+ ]
168
+ end
169
+ end
170
+ end
171
+ end
172
+
173
+ include AttrAccessors
174
+ include AttrInheritance
175
+ include AttrTypeCoercion
176
+
177
+ # The debug mode (boolean). The default value is +false+.
178
+ #
179
+ # When #debug is +true+:
180
+ # * Data sent to and received from the server will be logged.
181
+ # * ResponseParser will print warnings with extra detail for parse
182
+ # errors. _This may include recoverable errors._
183
+ # * ResponseParser makes extra assertions.
184
+ #
185
+ # *NOTE:* Versioned default configs inherit #debug from Config.global, and
186
+ # #load_defaults will not override #debug.
187
+ attr_accessor :debug, type: :boolean
188
+
189
+ # method: debug?
190
+ # :call-seq: debug? -> boolean
191
+ #
192
+ # Alias for #debug
193
+
194
+ # Seconds to wait until a connection is opened.
195
+ #
196
+ # If the IMAP object cannot open a connection within this time,
197
+ # it raises a Net::OpenTimeout exception.
198
+ #
199
+ # See Net::IMAP.new.
200
+ #
201
+ # The default value is +30+ seconds.
202
+ attr_accessor :open_timeout, type: Integer
203
+
204
+ # Seconds to wait until an IDLE response is received, after
205
+ # the client asks to leave the IDLE state.
206
+ #
207
+ # See Net::IMAP#idle and Net::IMAP#idle_done.
208
+ #
209
+ # The default value is +5+ seconds.
210
+ attr_accessor :idle_response_timeout, type: Integer
211
+
212
+ # Whether to use the +SASL-IR+ extension when the server and \SASL
213
+ # mechanism both support it. Can be overridden by the +sasl_ir+ keyword
214
+ # parameter to Net::IMAP#authenticate.
215
+ #
216
+ # <em>(Support for +SASL-IR+ was added in +v0.4.0+.)</em>
217
+ #
218
+ # ==== Valid options
219
+ #
220
+ # [+false+ <em>(original behavior, before support was added)</em>]
221
+ # Do not use +SASL-IR+, even when it is supported by the server and the
222
+ # mechanism.
223
+ #
224
+ # [+true+ <em>(default since +v0.4+)</em>]
225
+ # Use +SASL-IR+ when it is supported by the server and the mechanism.
226
+ attr_accessor :sasl_ir, type: :boolean
227
+
228
+ # Controls the behavior of Net::IMAP#login when the +LOGINDISABLED+
229
+ # capability is present. When enforced, Net::IMAP will raise a
230
+ # LoginDisabledError when that capability is present.
231
+ #
232
+ # <em>(Support for +LOGINDISABLED+ was added in +v0.5.0+.)</em>
233
+ #
234
+ # ==== Valid options
235
+ #
236
+ # [+false+ <em>(original behavior, before support was added)</em>]
237
+ # Send the +LOGIN+ command without checking for +LOGINDISABLED+.
238
+ #
239
+ # [+:when_capabilities_cached+]
240
+ # Enforce the requirement when Net::IMAP#capabilities_cached? is true,
241
+ # but do not send a +CAPABILITY+ command to discover the capabilities.
242
+ #
243
+ # [+true+ <em>(default since +v0.5+)</em>]
244
+ # Only send the +LOGIN+ command if the +LOGINDISABLED+ capability is not
245
+ # present. When capabilities are unknown, Net::IMAP will automatically
246
+ # send a +CAPABILITY+ command first before sending +LOGIN+.
247
+ #
248
+ attr_accessor :enforce_logindisabled, type: [
249
+ false, :when_capabilities_cached, true
250
+ ]
251
+
252
+ # Controls the behavior of Net::IMAP#responses when called without any
253
+ # arguments (+type+ or +block+).
254
+ #
255
+ # ==== Valid options
256
+ #
257
+ # [+:silence_deprecation_warning+ <em>(original behavior)</em>]
258
+ # Returns the mutable responses hash (without any warnings).
259
+ # <em>This is not thread-safe.</em>
260
+ #
261
+ # [+:warn+ <em>(default since +v0.5+)</em>]
262
+ # Prints a warning and returns the mutable responses hash.
263
+ # <em>This is not thread-safe.</em>
264
+ #
265
+ # [+:frozen_dup+ <em>(planned default for +v0.6+)</em>]
266
+ # Returns a frozen copy of the unhandled responses hash, with frozen
267
+ # array values.
268
+ #
269
+ # Note that calling IMAP#responses with a +type+ and without a block is
270
+ # not configurable and always behaves like +:frozen_dup+.
271
+ #
272
+ # <em>(+:frozen_dup+ config option was added in +v0.4.17+)</em>
273
+ #
274
+ # [+:raise+]
275
+ # Raise an ArgumentError with the deprecation warning.
276
+ #
277
+ # Note: #responses_without_args is an alias for #responses_without_block.
278
+ attr_accessor :responses_without_block, type: [
279
+ :silence_deprecation_warning, :warn, :frozen_dup, :raise,
280
+ ]
281
+
282
+ alias responses_without_args responses_without_block # :nodoc:
283
+ alias responses_without_args= responses_without_block= # :nodoc:
284
+
285
+ ##
286
+ # :attr_accessor: responses_without_args
287
+ #
288
+ # Alias for responses_without_block
289
+
290
+ # Creates a new config object and initialize its attribute with +attrs+.
291
+ #
292
+ # If +parent+ is not given, the global config is used by default.
293
+ #
294
+ # If a block is given, the new config object is yielded to it.
295
+ def initialize(parent = Config.global, **attrs)
296
+ super(parent)
297
+ update(**attrs)
298
+ yield self if block_given?
299
+ end
300
+
301
+ # :call-seq: update(**attrs) -> self
302
+ #
303
+ # Assigns all of the provided +attrs+ to this config, and returns +self+.
304
+ #
305
+ # An ArgumentError is raised unless every key in +attrs+ matches an
306
+ # assignment method on Config.
307
+ #
308
+ # >>>
309
+ # *NOTE:* #update is not atomic. If an exception is raised due to an
310
+ # invalid attribute value, +attrs+ may be partially applied.
311
+ def update(**attrs)
312
+ unless (bad = attrs.keys.reject { respond_to?(:"#{_1}=") }).empty?
313
+ raise ArgumentError, "invalid config options: #{bad.join(", ")}"
314
+ end
315
+ attrs.each do send(:"#{_1}=", _2) end
316
+ self
317
+ end
318
+
319
+ # :call-seq:
320
+ # with(**attrs) -> config
321
+ # with(**attrs) {|config| } -> result
322
+ #
323
+ # Without a block, returns a new config which inherits from self. With a
324
+ # block, yields the new config and returns the block's result.
325
+ #
326
+ # If no keyword arguments are given, an ArgumentError will be raised.
327
+ #
328
+ # If +self+ is frozen, the copy will also be frozen.
329
+ def with(**attrs)
330
+ attrs.empty? and
331
+ raise ArgumentError, "expected keyword arguments, none given"
332
+ copy = new(**attrs)
333
+ copy.freeze if frozen?
334
+ block_given? ? yield(copy) : copy
335
+ end
336
+
337
+ # :call-seq: load_defaults(version) -> self
338
+ #
339
+ # Resets the current config to behave like the versioned default
340
+ # configuration for +version+. #parent will not be changed.
341
+ #
342
+ # Some config attributes default to inheriting from their #parent (which
343
+ # is usually Config.global) and are left unchanged, for example: #debug.
344
+ #
345
+ # See Config@Versioned+defaults and Config@Named+defaults.
346
+ def load_defaults(version)
347
+ [Numeric, Symbol, String].any? { _1 === version } or
348
+ raise ArgumentError, "expected number or symbol, got %p" % [version]
349
+ update(**Config[version].defaults_hash)
350
+ end
351
+
352
+ # :call-seq: to_h -> hash
353
+ #
354
+ # Returns all config attributes in a hash.
355
+ def to_h; data.members.to_h { [_1, send(_1)] } end
356
+
357
+ protected
358
+
359
+ def defaults_hash
360
+ to_h.reject {|k,v| DEFAULT_TO_INHERIT.include?(k) }
361
+ end
362
+
363
+ @default = new(
364
+ debug: false,
365
+ open_timeout: 30,
366
+ idle_response_timeout: 5,
367
+ sasl_ir: true,
368
+ enforce_logindisabled: true,
369
+ responses_without_block: :warn,
370
+ ).freeze
371
+
372
+ @global = default.new
373
+
374
+ version_defaults[:default] = Config[default.send(:defaults_hash)]
375
+ version_defaults[:current] = Config[:default]
376
+
377
+ version_defaults[0] = Config[:current].dup.update(
378
+ sasl_ir: false,
379
+ responses_without_block: :silence_deprecation_warning,
380
+ enforce_logindisabled: false,
381
+ ).freeze
382
+ version_defaults[0.0] = Config[0]
383
+ version_defaults[0.1] = Config[0]
384
+ version_defaults[0.2] = Config[0]
385
+ version_defaults[0.3] = Config[0]
386
+
387
+ version_defaults[0.4] = Config[0.3].dup.update(
388
+ sasl_ir: true,
389
+ ).freeze
390
+
391
+ version_defaults[0.5] = Config[:current]
392
+
393
+ version_defaults[0.6] = Config[0.5].dup.update(
394
+ responses_without_block: :frozen_dup,
395
+ ).freeze
396
+ version_defaults[:next] = Config[0.6]
397
+ version_defaults[:future] = Config[:next]
398
+
399
+ version_defaults.freeze
400
+ end
401
+ end
402
+ end
@@ -186,7 +186,7 @@ module Net
186
186
 
187
187
  # Ensure argument is 'number' or raise DataFormatError
188
188
  def ensure_number(num)
189
- return if valid_number?(num)
189
+ return num if valid_number?(num)
190
190
 
191
191
  msg = "number must be unsigned 32-bit integer: #{num}"
192
192
  raise DataFormatError, msg
@@ -194,7 +194,7 @@ module Net
194
194
 
195
195
  # Ensure argument is 'nz_number' or raise DataFormatError
196
196
  def ensure_nz_number(num)
197
- return if valid_nz_number?(num)
197
+ return num if valid_nz_number?(num)
198
198
 
199
199
  msg = "nz_number must be non-zero unsigned 32-bit integer: #{num}"
200
200
  raise DataFormatError, msg
@@ -202,7 +202,7 @@ module Net
202
202
 
203
203
  # Ensure argument is 'mod_sequence_value' or raise DataFormatError
204
204
  def ensure_mod_sequence_value(num)
205
- return if valid_mod_sequence_value?(num)
205
+ return num if valid_mod_sequence_value?(num)
206
206
 
207
207
  msg = "mod_sequence_value must be unsigned 64-bit integer: #{num}"
208
208
  raise DataFormatError, msg
@@ -0,0 +1,226 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Some of the code in this file was copied from the polyfill-data gem.
4
+ #
5
+ # MIT License
6
+ #
7
+ # Copyright (c) 2023 Jim Gay, Joel Drapper, Nicholas Evans
8
+ #
9
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ # of this software and associated documentation files (the "Software"), to deal
11
+ # in the Software without restriction, including without limitation the rights
12
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ # copies of the Software, and to permit persons to whom the Software is
14
+ # furnished to do so, subject to the following conditions:
15
+ #
16
+ # The above copyright notice and this permission notice shall be included in all
17
+ # copies or substantial portions of the Software.
18
+ #
19
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ # SOFTWARE.
26
+
27
+
28
+ module Net
29
+ class IMAP
30
+ data_or_object = RUBY_VERSION >= "3.2.0" ? ::Data : Object
31
+ class DataLite < data_or_object
32
+ def encode_with(coder) coder.map = to_h.transform_keys(&:to_s) end
33
+ def init_with(coder) initialize(**coder.map.transform_keys(&:to_sym)) end
34
+ end
35
+
36
+ Data = DataLite
37
+ end
38
+ end
39
+
40
+ # :nocov:
41
+ # Need to skip test coverage for the rest, because it isn't loaded by ruby 3.2+.
42
+ return if RUBY_VERSION >= "3.2.0"
43
+
44
+ module Net
45
+ class IMAP
46
+ # DataLite is a temporary substitute for ruby 3.2's +Data+ class. DataLite
47
+ # is aliased as Net::IMAP::Data, so that code using it won't need to be
48
+ # updated when it is removed.
49
+ #
50
+ # See {ruby 3.2's documentation for Data}[https://docs.ruby-lang.org/en/3.2/Data.html].
51
+ #
52
+ # [When running ruby 3.1]
53
+ # This class reimplements the API for ruby 3.2's +Data+, and should be
54
+ # compatible for nearly all use-cases. This reimplementation <em>will be
55
+ # removed</em> in +net-imap+ 0.6, when support for ruby 3.1 is dropped.
56
+ #
57
+ # _NOTE:_ +net-imap+ no longer supports ruby versions prior to 3.1.
58
+ # [When running ruby >= 3.2]
59
+ # This class inherits from +Data+ and _only_ defines the methods needed
60
+ # for YAML serialization. This will be dropped when +psych+ adds support
61
+ # for +Data+.
62
+ #
63
+ # Some of the code in this class was copied or adapted from the
64
+ # {polyfill-data gem}[https://rubygems.org/gems/polyfill-data], by Jim Gay
65
+ # and Joel Drapper, under the MIT license terms.
66
+ class DataLite
67
+ singleton_class.undef_method :new
68
+
69
+ TYPE_ERROR = "%p is not a symbol nor a string"
70
+ ATTRSET_ERROR = "invalid data member: %p"
71
+ DUP_ERROR = "duplicate member: %p"
72
+ ARITY_ERROR = "wrong number of arguments (given %d, expected %s)"
73
+ private_constant :TYPE_ERROR, :ATTRSET_ERROR, :DUP_ERROR, :ARITY_ERROR
74
+
75
+ # Defines a new Data class.
76
+ #
77
+ # _NOTE:_ Unlike ruby 3.2's +Data.define+, DataLite.define only supports
78
+ # member names which are valid local variable names. Member names can't
79
+ # be keywords (e.g: +next+ or +class+) or start with capital letters, "@",
80
+ # etc.
81
+ def self.define(*args, &block)
82
+ members = args.each_with_object({}) do |arg, members|
83
+ arg = arg.to_str unless arg in Symbol | String if arg.respond_to?(:to_str)
84
+ arg = arg.to_sym if arg in String
85
+ arg in Symbol or raise TypeError, TYPE_ERROR % [arg]
86
+ arg in %r{=} and raise ArgumentError, ATTRSET_ERROR % [arg]
87
+ members.key?(arg) and raise ArgumentError, DUP_ERROR % [arg]
88
+ members[arg] = true
89
+ end
90
+ members = members.keys.freeze
91
+
92
+ klass = ::Class.new(self)
93
+
94
+ klass.singleton_class.undef_method :define
95
+ klass.define_singleton_method(:members) { members }
96
+
97
+ def klass.new(*args, **kwargs, &block)
98
+ if kwargs.size.positive?
99
+ if args.size.positive?
100
+ raise ArgumentError, ARITY_ERROR % [args.size, 0]
101
+ end
102
+ elsif members.size < args.size
103
+ expected = members.size.zero? ? 0 : 0..members.size
104
+ raise ArgumentError, ARITY_ERROR % [args.size, expected]
105
+ else
106
+ kwargs = Hash[members.take(args.size).zip(args)]
107
+ end
108
+ allocate.tap do |instance|
109
+ instance.__send__(:initialize, **kwargs, &block)
110
+ end.freeze
111
+ end
112
+
113
+ klass.singleton_class.alias_method :[], :new
114
+ klass.attr_reader(*members)
115
+
116
+ # Dynamically defined initializer methods are in an included module,
117
+ # rather than directly on DataLite (like in ruby 3.2+):
118
+ # * simpler to handle required kwarg ArgumentErrors
119
+ # * easier to ensure consistent ivar assignment order (object shape)
120
+ # * faster than instance_variable_set
121
+ klass.include(Module.new do
122
+ if members.any?
123
+ kwargs = members.map{"#{_1.name}:"}.join(", ")
124
+ params = members.map(&:name).join(", ")
125
+ ivars = members.map{"@#{_1.name}"}.join(", ")
126
+ attrs = members.map{"attrs[:#{_1.name}]"}.join(", ")
127
+ module_eval <<~RUBY, __FILE__, __LINE__ + 1
128
+ protected
129
+ def initialize(#{kwargs}) #{ivars} = #{params}; freeze end
130
+ def marshal_load(attrs) #{ivars} = #{attrs}; freeze end
131
+ RUBY
132
+ end
133
+ end)
134
+
135
+ klass.module_eval do _1.module_eval(&block) end if block_given?
136
+
137
+ klass
138
+ end
139
+
140
+ ##
141
+ # singleton-method: new
142
+ # call-seq:
143
+ # new(*args) -> instance
144
+ # new(**kwargs) -> instance
145
+ #
146
+ # Constuctor for classes defined with ::define.
147
+ #
148
+ # Aliased as ::[].
149
+
150
+ ##
151
+ # singleton-method: []
152
+ # call-seq:
153
+ # ::[](*args) -> instance
154
+ # ::[](**kwargs) -> instance
155
+ #
156
+ # Constuctor for classes defined with ::define.
157
+ #
158
+ # Alias for ::new
159
+
160
+ ##
161
+ def members; self.class.members end
162
+ def to_h(&block) block ? __to_h__.to_h(&block) : __to_h__ end
163
+ def hash; [self.class, __to_h__].hash end
164
+ def ==(other) self.class == other.class && to_h == other.to_h end
165
+ def eql?(other) self.class == other.class && hash == other.hash end
166
+ def deconstruct; __to_h__.values end
167
+
168
+ def deconstruct_keys(keys)
169
+ raise TypeError unless keys.is_a?(Array) || keys.nil?
170
+ return __to_h__ if keys&.first.nil?
171
+ __to_h__.slice(*keys)
172
+ end
173
+
174
+ def with(**kwargs)
175
+ return self if kwargs.empty?
176
+ self.class.new(**__to_h__.merge(kwargs))
177
+ end
178
+
179
+ def inspect
180
+ __inspect_guard__(self) do |seen|
181
+ return "#<data #{self.class}:...>" if seen
182
+ attrs = __to_h__.map {|kv| "%s=%p" % kv }.join(", ")
183
+ display = ["data", self.class.name, attrs].compact.join(" ")
184
+ "#<#{display}>"
185
+ end
186
+ end
187
+ alias_method :to_s, :inspect
188
+
189
+ private
190
+
191
+ def initialize_copy(source) super.freeze end
192
+ def marshal_dump; __to_h__ end
193
+
194
+ def __to_h__; Hash[members.map {|m| [m, send(m)] }] end
195
+
196
+ # Yields +true+ if +obj+ has been seen already, +false+ if it hasn't.
197
+ # Marks +obj+ as seen inside the block, so circuler references don't
198
+ # recursively trigger a SystemStackError (stack level too deep).
199
+ #
200
+ # Making circular references inside a Data object _should_ be very
201
+ # uncommon, but we'll support them for the sake of completeness.
202
+ def __inspect_guard__(obj)
203
+ preexisting = Thread.current[:__net_imap_data__inspect__]
204
+ Thread.current[:__net_imap_data__inspect__] ||= {}.compare_by_identity
205
+ inspect_guard = Thread.current[:__net_imap_data__inspect__]
206
+ if inspect_guard.include?(obj)
207
+ yield true
208
+ else
209
+ begin
210
+ inspect_guard[obj] = true
211
+ yield false
212
+ ensure
213
+ inspect_guard.delete(obj)
214
+ end
215
+ end
216
+ ensure
217
+ unless preexisting.equal?(inspect_guard)
218
+ Thread.current[:__net_imap_data__inspect__] = preexisting
219
+ end
220
+ end
221
+
222
+ end
223
+
224
+ end
225
+ end
226
+ # :nocov:
@@ -16,8 +16,8 @@ module Net
16
16
  #
17
17
  # ==== Obsolete arguments
18
18
  #
19
- # Using obsolete arguments does not a warning. Obsolete arguments will be
20
- # deprecated by a future release.
19
+ # Use of obsolete arguments does not print a warning. Obsolete arguments
20
+ # will be deprecated by a future release.
21
21
  #
22
22
  # If a second positional argument is given and it is a hash (or is
23
23
  # convertible via +#to_hash+), it is converted to keyword arguments.
@@ -83,10 +83,12 @@ module Net
83
83
  elsif deprecated.empty?
84
84
  super host, port: port_or_options
85
85
  elsif deprecated.shift
86
- warn "DEPRECATED: Call Net::IMAP.new with keyword options", uplevel: 1
86
+ warn("DEPRECATED: Call Net::IMAP.new with keyword options",
87
+ uplevel: 1, category: :deprecated)
87
88
  super host, port: port_or_options, ssl: create_ssl_params(*deprecated)
88
89
  else
89
- warn "DEPRECATED: Call Net::IMAP.new with keyword options", uplevel: 1
90
+ warn("DEPRECATED: Call Net::IMAP.new with keyword options",
91
+ uplevel: 1, category: :deprecated)
90
92
  super host, port: port_or_options, ssl: false
91
93
  end
92
94
  end
@@ -113,7 +115,8 @@ module Net
113
115
  elsif deprecated.first.respond_to?(:to_hash)
114
116
  super(**Hash.try_convert(deprecated.first))
115
117
  else
116
- warn "DEPRECATED: Call Net::IMAP#starttls with keyword options", uplevel: 1
118
+ warn("DEPRECATED: Call Net::IMAP#starttls with keyword options",
119
+ uplevel: 1, category: :deprecated)
117
120
  super(**create_ssl_params(*deprecated))
118
121
  end
119
122
  end