diff --git a/lib/net/imap/config.rb b/lib/net/imap/config.rb
index 370ff719..ea7602ee 100644
--- a/lib/net/imap/config.rb
+++ b/lib/net/imap/config.rb
@@ -3,6 +3,7 @@
require_relative "config/attr_accessors"
require_relative "config/attr_inheritance"
require_relative "config/attr_type_coercion"
+require_relative "config/attr_version_defaults"
module Net
class IMAP
@@ -141,15 +142,7 @@ def self.global; @global if defined?(@global) end
# Net::IMAP::Config[0.5] == Net::IMAP::Config[0.5r] # => true
# Net::IMAP::Config["current"] == Net::IMAP::Config[:current] # => true
# Net::IMAP::Config["0.5.6"] == Net::IMAP::Config[0.5r] # => true
- def self.version_defaults; @version_defaults end
- @version_defaults = Hash.new {|h, k|
- # NOTE: String responds to both so the order is significant.
- # And ignore non-numeric conversion to zero, because: "wat!?".to_r == 0
- (h.fetch(k.to_r, nil) || h.fetch(k.to_f, nil) if k.is_a?(Numeric)) ||
- (h.fetch(k.to_sym, nil) if k.respond_to?(:to_sym)) ||
- (h.fetch(k.to_r, nil) if k.respond_to?(:to_r) && k.to_r != 0r) ||
- (h.fetch(k.to_f, nil) if k.respond_to?(:to_f) && k.to_f != 0.0)
- }
+ def self.version_defaults; AttrVersionDefaults.version_defaults end
# :call-seq:
# Net::IMAP::Config[number] -> versioned config
@@ -189,6 +182,7 @@ def self.[](config)
include AttrAccessors
include AttrInheritance
include AttrTypeCoercion
+ extend AttrVersionDefaults
# The debug mode (boolean). The default value is +false+.
#
@@ -200,7 +194,7 @@ def self.[](config)
#
# *NOTE:* Versioned default configs inherit #debug from Config.global, and
# #load_defaults will not override #debug.
- attr_accessor :debug, type: :boolean
+ attr_accessor :debug, type: :boolean, default: false
# method: debug?
# :call-seq: debug? -> boolean
@@ -218,7 +212,7 @@ def self.[](config)
# See Net::IMAP.new and Net::IMAP#starttls.
#
# The default value is +30+ seconds.
- attr_accessor :open_timeout, type: Integer
+ attr_accessor :open_timeout, type: Integer, default: 30
# Seconds to wait until an IDLE response is received, after
# the client asks to leave the IDLE state.
@@ -226,7 +220,7 @@ def self.[](config)
# See Net::IMAP#idle and Net::IMAP#idle_done.
#
# The default value is +5+ seconds.
- attr_accessor :idle_response_timeout, type: Integer
+ attr_accessor :idle_response_timeout, type: Integer, default: 5
# Whether to use the +SASL-IR+ extension when the server and \SASL
# mechanism both support it. Can be overridden by the +sasl_ir+ keyword
@@ -242,7 +236,10 @@ def self.[](config)
#
# [+true+ (default since +v0.4+)]
# Use +SASL-IR+ when it is supported by the server and the mechanism.
- attr_accessor :sasl_ir, type: :boolean
+ attr_accessor :sasl_ir, type: :boolean, defaults: {
+ 0.0r => false,
+ 0.4r => true,
+ }
# Controls the behavior of Net::IMAP#login when the +LOGINDISABLED+
# capability is present. When enforced, Net::IMAP will raise a
@@ -266,7 +263,10 @@ def self.[](config)
#
attr_accessor :enforce_logindisabled, type: Enum[
false, :when_capabilities_cached, true
- ]
+ ], defaults: {
+ 0.0r => false,
+ 0.5r => true,
+ }
# The maximum allowed server response size. When +nil+, there is no limit
# on response size.
@@ -300,7 +300,10 @@ def self.[](config)
#
# * original: +nil+ (no limit)
# * +0.5+: 512 MiB
- attr_accessor :max_response_size, type: Integer?
+ attr_accessor :max_response_size, type: Integer?, defaults: {
+ 0.0r => nil,
+ 0.5r => 512 << 20, # 512 MiB
+ }
# Controls the behavior of Net::IMAP#responses when called without any
# arguments (+type+ or +block+).
@@ -330,7 +333,11 @@ def self.[](config)
# Note: #responses_without_args is an alias for #responses_without_block.
attr_accessor :responses_without_block, type: Enum[
:silence_deprecation_warning, :warn, :frozen_dup, :raise,
- ]
+ ], defaults: {
+ 0.0r => :silence_deprecation_warning,
+ 0.5r => :warn,
+ 0.6r => :frozen_dup,
+ }
alias responses_without_args responses_without_block # :nodoc:
alias responses_without_args= responses_without_block= # :nodoc:
@@ -375,7 +382,11 @@ def self.[](config)
# ResponseParser _only_ uses AppendUIDData and CopyUIDData.
attr_accessor :parser_use_deprecated_uidplus_data, type: Enum[
true, :up_to_max_size, false
- ]
+ ], defaults: {
+ 0.0r => true,
+ 0.5r => :up_to_max_size,
+ 0.6r => false,
+ }
# The maximum +uid-set+ size that ResponseParser will parse into
# deprecated UIDPlusData. This limit only applies when
@@ -399,7 +410,13 @@ def self.[](config)
# * +0.5+: 100
# * +0.6+: 0
#
- attr_accessor :parser_max_deprecated_uidplus_data_size, type: Integer
+ attr_accessor :parser_max_deprecated_uidplus_data_size, type: Integer,
+ defaults: {
+ 0.0r => 10_000,
+ 0.4r => 1_000,
+ 0.5r => 100,
+ 0.6r => 0,
+ }
# Creates a new config object and initialize its attribute with +attrs+.
#
@@ -474,82 +491,10 @@ def defaults_hash
to_h.reject {|k,v| DEFAULT_TO_INHERIT.include?(k) }
end
- @default = new(
- debug: false,
- open_timeout: 30,
- idle_response_timeout: 5,
- sasl_ir: true,
- enforce_logindisabled: true,
- max_response_size: 512 << 20, # 512 MiB
- responses_without_block: :frozen_dup,
- parser_use_deprecated_uidplus_data: false,
- parser_max_deprecated_uidplus_data_size: 0,
- ).freeze
-
- @global = default.new
-
- version_defaults[:default] = Config[default.send(:defaults_hash)]
-
- version_defaults[0r] = Config[:default].dup.update(
- sasl_ir: false,
- responses_without_block: :silence_deprecation_warning,
- enforce_logindisabled: false,
- max_response_size: nil,
- parser_use_deprecated_uidplus_data: true,
- parser_max_deprecated_uidplus_data_size: 10_000,
- ).freeze
- version_defaults[0.0r] = Config[0r]
- version_defaults[0.1r] = Config[0r]
- version_defaults[0.2r] = Config[0r]
- version_defaults[0.3r] = Config[0r]
-
- version_defaults[0.4r] = Config[0.3r].dup.update(
- sasl_ir: true,
- parser_max_deprecated_uidplus_data_size: 1000,
- ).freeze
-
- version_defaults[0.5r] = Config[0.4r].dup.update(
- enforce_logindisabled: true,
- max_response_size: 512 << 20, # 512 MiB
- responses_without_block: :warn,
- parser_use_deprecated_uidplus_data: :up_to_max_size,
- parser_max_deprecated_uidplus_data_size: 100,
- ).freeze
-
- version_defaults[0.6r] = Config[0.5r].dup.update(
- responses_without_block: :frozen_dup,
- parser_use_deprecated_uidplus_data: false,
- parser_max_deprecated_uidplus_data_size: 0,
- ).freeze
-
- version_defaults[0.7r] = Config[0.6r].dup.update(
- ).freeze
-
- version_defaults[0.8r] = Config[0.7r].dup.update(
- ).freeze
-
- # Safe conversions one way only:
- # 0.6r.to_f == 0.6 # => true
- # 0.6 .to_r == 0.6r # => false
- version_defaults.to_a.each do |k, v|
- next unless k in Rational
- version_defaults[k.to_f] = v
- end
-
- current = VERSION.to_r
- version_defaults[:original] = Config[0]
- version_defaults[:current] = Config[current]
- version_defaults[:next] = Config[current + 0.1r]
- version_defaults[:future] = Config[current + 0.2r]
-
- version_defaults.freeze
+ @default = AttrVersionDefaults.compile_default!
+ @global = default.new
+ AttrVersionDefaults.compile_version_defaults!
- if ($VERBOSE || $DEBUG) && self[:current].to_h != self[:default].to_h
- warn "Misconfigured Net::IMAP::Config[:current] => %p,\n" \
- " not equal to Net::IMAP::Config[:default] => %p" % [
- self[:current].to_h, self[:default].to_h
- ]
- end
end
end
end
diff --git a/lib/net/imap/config/attr_version_defaults.rb b/lib/net/imap/config/attr_version_defaults.rb
new file mode 100644
index 00000000..6457bddb
--- /dev/null
+++ b/lib/net/imap/config/attr_version_defaults.rb
@@ -0,0 +1,93 @@
+# frozen_string_literal: true
+
+require "forwardable"
+
+module Net
+ class IMAP
+ class Config
+ # >>>
+ # *NOTE:* This module is an internal implementation detail, with no
+ # guarantee of backward compatibility.
+ #
+ # Adds a +defaults+ parameter to +attr_accessor+, which is used to compile
+ # Config.version_defaults.
+ module AttrVersionDefaults
+ # The x.y part of Net::IMAP::VERSION, as a Rational number.
+ CURRENT_VERSION = VERSION.to_r
+
+ # The config version used for Config[:next].
+ NEXT_VERSION = CURRENT_VERSION + 0.1r
+
+ # The config version used for Config[:future].
+ FUTURE_VERSION = 1.0r
+
+ VERSIONS = ((0.0r..FUTURE_VERSION) % 0.1r).to_a.freeze
+
+ # See Config.version_defaults.
+ singleton_class.attr_accessor :version_defaults
+
+ @version_defaults = Hash.new {|h, k|
+ # NOTE: String responds to both so the order is significant.
+ # And ignore non-numeric conversion to zero, because: "wat!?".to_r == 0
+ (h.fetch(k.to_r, nil) || h.fetch(k.to_f, nil) if k.is_a?(Numeric)) ||
+ (h.fetch(k.to_sym, nil) if k.respond_to?(:to_sym)) ||
+ (h.fetch(k.to_r, nil) if k.respond_to?(:to_r) && k.to_r != 0r) ||
+ (h.fetch(k.to_f, nil) if k.respond_to?(:to_f) && k.to_f != 0.0)
+ }
+
+ # :stopdoc: internal APIs only
+
+ def attr_accessor(name, defaults: nil, default: (unset = true), **kw)
+ unless unset
+ version = DEFAULT_TO_INHERIT.include?(name) ? nil : 0.0r
+ defaults = { version => default }
+ end
+ defaults&.each_pair do |version, default|
+ AttrVersionDefaults.version_defaults[version] ||= {}
+ AttrVersionDefaults.version_defaults[version][name] = default
+ end
+ super(name, **kw)
+ end
+
+ def self.compile_default!
+ raise "Config.default already compiled" if Config.default
+ default = VERSIONS.select { _1 <= CURRENT_VERSION }
+ .filter_map { version_defaults[_1] }
+ .prepend(version_defaults.delete(nil))
+ .inject(&:merge)
+ Config.new(**default).freeze
+ end
+
+ def self.compile_version_defaults!
+ # Temporarily assign Config.default, enabling #load_defaults(:default)
+ version_defaults[:default] = Config.default
+ # Use #load_defaults so some attributes are inherited from global.
+ version_defaults[:default] = Config.new.load_defaults(:default).freeze
+ version_defaults[0.0r] = Config[version_defaults.fetch(0.0r)]
+
+ VERSIONS.each_cons(2) do |prior, version|
+ updates = version_defaults[version]
+ version_defaults[version] = version_defaults[prior]
+ .then { updates ? _1.dup.update(**updates).freeze : _1 }
+ end
+
+ # Safe conversions one way only:
+ # 0.6r.to_f == 0.6 # => true
+ # 0.6 .to_r == 0.6r # => false
+ version_defaults.to_a.each do |k, v|
+ next unless k in Rational
+ version_defaults[k.to_f] = v
+ end
+
+ version_defaults[:original] = Config[0.0r]
+ version_defaults[:current] = Config[CURRENT_VERSION]
+ version_defaults[:next] = Config[NEXT_VERSION]
+ version_defaults[:future] = Config[FUTURE_VERSION]
+
+ version_defaults.freeze
+ end
+
+ end
+ end
+ end
+end
diff --git a/test/net/imap/test_config.rb b/test/net/imap/test_config.rb
index 05618c40..9290cd28 100644
--- a/test/net/imap/test_config.rb
+++ b/test/net/imap/test_config.rb
@@ -7,7 +7,7 @@ class ConfigTest < Net::IMAP::TestCase
Config = Net::IMAP::Config
THIS_VERSION = Net::IMAP::VERSION.to_f
NEXT_VERSION = THIS_VERSION + 0.1
- FUTURE_VERSION = THIS_VERSION + 0.2
+ FUTURE_VERSION = 1.0
setup do
Config.global.reset
@@ -72,6 +72,7 @@ class ConfigTest < Net::IMAP::TestCase
test ".default" do
default = Config.default
assert default.equal?(Config.default)
+ assert_nil default.parent
assert default.is_a?(Config)
assert default.frozen?
refute default.debug?
@@ -184,8 +185,7 @@ class ConfigTest < Net::IMAP::TestCase
assert_raise(RangeError) do Config[0.01] end
assert_raise(RangeError) do Config[0.11] end
assert_raise(RangeError) do Config[0.111] end
- assert_raise(RangeError) do Config[0.9] end
- assert_raise(RangeError) do Config[1] end
+ assert_raise(RangeError) do Config[1.1] end
end
test ".[] key errors" do