diff --git a/lib/net/imap/config.rb b/lib/net/imap/config.rb index cbe62841..5cff7b87 100644 --- a/lib/net/imap/config.rb +++ b/lib/net/imap/config.rb @@ -54,22 +54,66 @@ class IMAP # plain_client.config.inherited?(:debug) # => true # plain_client.config.debug? # => false # + # == Versioned defaults + # + # The effective default configuration for a specific +x.y+ version of + # +net-imap+ can be loaded with the +config+ keyword argument to + # Net::IMAP.new. Requesting default configurations for previous versions + # enables extra backward compatibility with those versions: + # + # client = Net::IMAP.new(hostname, config: 0.3) + # client.config.sasl_ir # => false + # client.config.responses_without_block # => :silence_deprecation_warning + # + # client = Net::IMAP.new(hostname, config: 0.4) + # client.config.sasl_ir # => true + # client.config.responses_without_block # => :silence_deprecation_warning + # + # client = Net::IMAP.new(hostname, config: 0.5) + # client.config.sasl_ir # => true + # client.config.responses_without_block # => :warn + # + # The versioned default configs inherit certain specific config options from + # Config.global, for example #debug: + # + # client = Net::IMAP.new(hostname, config: 0.4) + # Net::IMAP.debug = false + # client.config.debug? # => false + # + # Net::IMAP.debug = true + # client.config.debug? # => true # # == Thread Safety # # *NOTE:* Updates to config objects are not synchronized for thread-safety. # class Config + # Array of attribute names that are _not_ loaded by #load_defaults. + DEFAULT_TO_INHERIT = %i[debug].freeze + private_constant :DEFAULT_TO_INHERIT + # The default config, which is hardcoded and frozen. def self.default; @default end # The global config object. Also available from Net::IMAP.config. def self.global; @global end + # A hash of hard-coded configurations, indexed by version number. + def self.version_defaults; @version_defaults end + @version_defaults = {} + # :call-seq: + # Net::IMAP::Config[number] -> versioned config + # Net::IMAP::Config[symbol] -> named config # Net::IMAP::Config[hash] -> new frozen config # Net::IMAP::Config[config] -> same config # + # Given a version number, returns the default configuration for the target + # version. See Config@Versioned+defaults. + # + # Given a version name, returns the default configuration for the target + # version. See Config@Named+defaults. + # # Given a Hash, creates a new _frozen_ config which inherits from # Config.global. Use Config.new for an unfrozen config. # @@ -80,9 +124,16 @@ def self.[](config) elsif config.respond_to?(:to_hash) new(global, **config).freeze else - raise TypeError, "no implicit conversion of %s to %s" % [ - config.class, Config - ] + version_defaults.fetch(config) { + case config + when Numeric + raise RangeError, "unknown config version: %p" % [config] + else + raise TypeError, "no implicit conversion of %s to %s" % [ + config.class, Config + ] + end + } end end @@ -204,6 +255,23 @@ def to_h; data.members.to_h { [_1, send(_1)] } end @global = default.new + version_defaults[0.4] = Config[ + default.to_h.reject {|k,v| DEFAULT_TO_INHERIT.include?(k) } + ] + + version_defaults[0] = Config[0.4].dup.update( + sasl_ir: false, + ).freeze + version_defaults[0.0] = Config[0] + version_defaults[0.1] = Config[0] + version_defaults[0.2] = Config[0] + version_defaults[0.3] = Config[0] + + version_defaults[0.5] = Config[0.4].dup.update( + responses_without_block: :warn, + ).freeze + + version_defaults.freeze end end end diff --git a/test/net/imap/test_config.rb b/test/net/imap/test_config.rb index e288cbab..18659216 100644 --- a/test/net/imap/test_config.rb +++ b/test/net/imap/test_config.rb @@ -135,6 +135,35 @@ class ConfigTest < Test::Unit::TestCase assert_equal false, child.debug? end + test ".version_defaults are all frozen, and inherit debug from global" do + Config.version_defaults.each do |name, config| + assert [0, Float, Symbol].any? { _1 === name } + assert_kind_of Config, config + assert config.frozen?, "#{name} isn't frozen" + assert config.inherited?(:debug), "#{name} doesn't inherit debug" + assert_same Config.global, config.parent + end + end + + test ".[] for all x.y versions" do + original = Config[0] + assert_kind_of Config, original + assert_same original, Config[0.0] + assert_same original, Config[0.1] + assert_same original, Config[0.2] + assert_same original, Config[0.3] + assert_kind_of Config, Config[0.4] + assert_kind_of Config, Config[0.5] + end + + test ".[] range errors" do + 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 + end + test ".[] with a hash" do config = Config[{responses_without_block: :raise, sasl_ir: false}] assert config.frozen? @@ -149,6 +178,7 @@ class ConfigTest < Test::Unit::TestCase assert_same Config.global, Config.new.parent assert_same Config.default, Config.new(Config.default).parent assert_same Config.global, Config.new(Config.global).parent + assert_same Config[0.4], Config.new(0.4).parent assert_equal true, Config.new({debug: true}, debug: false).parent.debug? assert_equal true, Config.new({debug: true}, debug: false).parent.frozen? end