@@ -54,25 +54,112 @@ class IMAP
5454 # plain_client.config.inherited?(:debug) # => true
5555 # plain_client.config.debug? # => false
5656 #
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 # => :raise
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+ # === Named defaults
91+ # In addition to +x.y+ version numbers, the following aliases are supported:
92+ #
93+ # [+:default+]
94+ # An alias for +:current+.
95+ #
96+ # >>>
97+ # *NOTE*: This is _not_ the same as Config.default. It inherits some
98+ # attributes from Config.global, for example: #debug.
99+ # [+:current+]
100+ # An alias for the current +x.y+ version's defaults.
101+ # [+:next+]
102+ # The _planned_ config for the next +x.y+ version.
103+ # [+:future+]
104+ # The _planned_ eventual config for some future +x.y+ version.
105+ #
106+ # For example, to raise exceptions for all current deprecations:
107+ # client = Net::IMAP.new(hostname, config: :future)
108+ # client.responses # raises an ArgumentError
57109 #
58110 # == Thread Safety
59111 #
60112 # *NOTE:* Updates to config objects are not synchronized for thread-safety.
61113 #
62114 class Config
115+ # Array of attribute names that are _not_ loaded by #load_defaults.
116+ DEFAULT_TO_INHERIT = %i[ debug ] . freeze
117+ private_constant :DEFAULT_TO_INHERIT
118+
63119 # The default config, which is hardcoded and frozen.
64120 def self . default ; @default end
65121
66122 # The global config object. Also available from Net::IMAP.config.
67123 def self . global ; @global end
68124
69- def self . []( config ) # :nodoc: unfinished API
125+ # A hash of hard-coded configurations, indexed by version number.
126+ def self . version_defaults ; @version_defaults end
127+ @version_defaults = { }
128+
129+ # :call-seq:
130+ # Net::IMAP::Config[number] -> versioned config
131+ # Net::IMAP::Config[symbol] -> named config
132+ # Net::IMAP::Config[hash] -> new frozen config
133+ # Net::IMAP::Config[config] -> same config
134+ #
135+ # Given a version number, returns the default configuration for the target
136+ # version. See Config@Versioned+defaults.
137+ #
138+ # Given a version name, returns the default configuration for the target
139+ # version. See Config@Named+defaults.
140+ #
141+ # Given a Hash, creates a new _frozen_ config which inherits from
142+ # Config.global. Use Config.new for an unfrozen config.
143+ #
144+ # Given a config, returns that same config.
145+ def self . []( config )
70146 if config . is_a? ( Config ) || config . nil? && global . nil?
71147 config
148+ elsif config . respond_to? ( :to_hash )
149+ new ( global , **config ) . freeze
72150 else
73- raise TypeError , "no implicit conversion of %s to %s" % [
74- config . class , Config
75- ]
151+ version_defaults . fetch ( config ) {
152+ case config
153+ when Numeric
154+ raise RangeError , "unknown config version: %p" % [ config ]
155+ when Symbol
156+ raise KeyError , "unknown config name: %p" % [ config ]
157+ else
158+ raise TypeError , "no implicit conversion of %s to %s" % [
159+ config . class , Config
160+ ]
161+ end
162+ }
76163 end
77164 end
78165
@@ -198,6 +285,31 @@ def to_h; data.members.to_h { [_1, send(_1)] } end
198285
199286 @global = default . new
200287
288+ version_defaults [ 0.4 ] = Config [
289+ default . to_h . reject { |k , v | DEFAULT_TO_INHERIT . include? ( k ) }
290+ ]
291+
292+ version_defaults [ 0 ] = Config [ 0.4 ] . dup . update (
293+ sasl_ir : false ,
294+ ) . freeze
295+ version_defaults [ 0.0 ] = Config [ 0 ]
296+ version_defaults [ 0.1 ] = Config [ 0 ]
297+ version_defaults [ 0.2 ] = Config [ 0 ]
298+ version_defaults [ 0.3 ] = Config [ 0 ]
299+
300+ version_defaults [ 0.5 ] = Config [ 0.4 ] . dup . update (
301+ responses_without_block : :warn ,
302+ ) . freeze
303+
304+ version_defaults [ :default ] = Config [ 0.4 ]
305+ version_defaults [ :current ] = Config [ 0.4 ]
306+ version_defaults [ :next ] = Config [ 0.5 ]
307+
308+ version_defaults [ :future ] = Config [ 0.5 ] . dup . update (
309+ responses_without_block : :raise ,
310+ ) . freeze
311+
312+ version_defaults . freeze
201313 end
202314 end
203315end
0 commit comments