Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 38 additions & 93 deletions lib/net/imap/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -189,6 +182,7 @@ def self.[](config)
include AttrAccessors
include AttrInheritance
include AttrTypeCoercion
extend AttrVersionDefaults

# The debug mode (boolean). The default value is +false+.
#
Expand All @@ -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
Expand All @@ -218,15 +212,15 @@ 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.
#
# 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
Expand All @@ -242,7 +236,10 @@ def self.[](config)
#
# [+true+ <em>(default since +v0.4+)</em>]
# 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
Expand All @@ -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.
Expand Down Expand Up @@ -300,7 +300,10 @@ def self.[](config)
#
# * original: +nil+ <em>(no limit)</em>
# * +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+).
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -399,7 +410,13 @@ def self.[](config)
# * +0.5+: <tt>100</tt>
# * +0.6+: <tt>0</tt>
#
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+.
#
Expand Down Expand Up @@ -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
93 changes: 93 additions & 0 deletions lib/net/imap/config/attr_version_defaults.rb
Original file line number Diff line number Diff line change
@@ -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 <tt>x.y</tt> part of Net::IMAP::VERSION, as a Rational number.
CURRENT_VERSION = VERSION.to_r

# The config version used for <tt>Config[:next]</tt>.
NEXT_VERSION = CURRENT_VERSION + 0.1r

# The config version used for <tt>Config[:future]</tt>.
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
6 changes: 3 additions & 3 deletions test/net/imap/test_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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?
Expand Down Expand Up @@ -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
Expand Down
Loading