Skip to content

Commit 1e0a872

Browse files
authored
🔀 Merge pull request #430 from ruby/backport/v0.4-GH429-rational-config-version
♻️ Rational config version (backport #429 to v0.4)
2 parents 9dce78a + 417abc4 commit 1e0a872

File tree

2 files changed

+96
-19
lines changed

2 files changed

+96
-19
lines changed

lib/net/imap/config.rb

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,25 @@ def self.default; @default end
129129
def self.global; @global if defined?(@global) end
130130

131131
# A hash of hard-coded configurations, indexed by version number or name.
132+
# Values can be accessed with any object that responds to +to_sym+ or
133+
# +to_r+/+to_f+ with a non-zero number.
134+
#
135+
# Config::[] gets named or numbered versions from this hash.
136+
#
137+
# For example:
138+
# Net::IMAP::Config.version_defaults[0.5] == Net::IMAP::Config[0.5]
139+
# Net::IMAP::Config[0.5] == Net::IMAP::Config[0.5r] # => true
140+
# Net::IMAP::Config["current"] == Net::IMAP::Config[:current] # => true
141+
# Net::IMAP::Config["0.5.6"] == Net::IMAP::Config[0.5r] # => true
132142
def self.version_defaults; @version_defaults end
133-
@version_defaults = {}
143+
@version_defaults = Hash.new {|h, k|
144+
# NOTE: String responds to both so the order is significant.
145+
# And ignore non-numeric conversion to zero, because: "wat!?".to_r == 0
146+
(h.fetch(k.to_r, nil) || h.fetch(k.to_f, nil) if k.is_a?(Numeric)) ||
147+
(h.fetch(k.to_sym, nil) if k.respond_to?(:to_sym)) ||
148+
(h.fetch(k.to_r, nil) if k.respond_to?(:to_r) && k.to_r != 0r) ||
149+
(h.fetch(k.to_f, nil) if k.respond_to?(:to_f) && k.to_f != 0.0)
150+
}
134151

135152
# :call-seq:
136153
# Net::IMAP::Config[number] -> versioned config
@@ -153,18 +170,17 @@ def self.[](config)
153170
elsif config.nil? && global.nil? then nil
154171
elsif config.respond_to?(:to_hash) then new(global, **config).freeze
155172
else
156-
version_defaults.fetch(config) do
173+
version_defaults[config] or
157174
case config
158175
when Numeric
159176
raise RangeError, "unknown config version: %p" % [config]
160-
when Symbol
177+
when String, Symbol
161178
raise KeyError, "unknown config name: %p" % [config]
162179
else
163180
raise TypeError, "no implicit conversion of %s to %s" % [
164181
config.class, Config
165182
]
166183
end
167-
end
168184
end
169185
end
170186

@@ -413,41 +429,50 @@ def defaults_hash
413429

414430
version_defaults[:default] = Config[default.send(:defaults_hash)]
415431

416-
version_defaults[0] = Config[:default].dup.update(
432+
version_defaults[0r] = Config[:default].dup.update(
417433
sasl_ir: false,
418434
parser_use_deprecated_uidplus_data: true,
419435
parser_max_deprecated_uidplus_data_size: 10_000,
420436
).freeze
421-
version_defaults[0.0] = Config[0]
422-
version_defaults[0.1] = Config[0]
423-
version_defaults[0.2] = Config[0]
424-
version_defaults[0.3] = Config[0]
437+
version_defaults[0.0r] = Config[0r]
438+
version_defaults[0.1r] = Config[0r]
439+
version_defaults[0.2r] = Config[0r]
440+
version_defaults[0.3r] = Config[0r]
425441

426-
version_defaults[0.4] = Config[0.3].dup.update(
442+
version_defaults[0.4r] = Config[0.3r].dup.update(
427443
sasl_ir: true,
428444
parser_max_deprecated_uidplus_data_size: 1000,
429445
).freeze
430446

431-
version_defaults[0.5] = Config[0.4].dup.update(
447+
version_defaults[0.5r] = Config[0.4r].dup.update(
432448
responses_without_block: :warn,
433449
parser_use_deprecated_uidplus_data: :up_to_max_size,
434450
parser_max_deprecated_uidplus_data_size: 100,
435451
).freeze
436452

437-
version_defaults[0.6] = Config[0.5].dup.update(
453+
version_defaults[0.6r] = Config[0.5r].dup.update(
438454
responses_without_block: :frozen_dup,
439455
parser_use_deprecated_uidplus_data: false,
440456
parser_max_deprecated_uidplus_data_size: 0,
441457
).freeze
442458

443-
version_defaults[0.7] = Config[0.6].dup.update(
459+
version_defaults[0.7r] = Config[0.6r].dup.update(
444460
).freeze
445461

446-
current = VERSION.to_f
462+
# Safe conversions one way only:
463+
# 0.6r.to_f == 0.6 # => true
464+
# 0.6 .to_r == 0.6r # => false
465+
version_defaults.to_a.each do |k, v|
466+
next unless k.is_a? Rational
467+
version_defaults[k.to_f] = v
468+
end
469+
470+
current = VERSION.to_r
447471
version_defaults[:original] = Config[0]
448472
version_defaults[:current] = Config[current]
449-
version_defaults[:next] = Config[current + 0.1]
450-
version_defaults[:future] = Config[0.7]
473+
version_defaults[:next] = Config[current + 0.1r]
474+
475+
version_defaults[:future] = Config[0.7r]
451476

452477
version_defaults.freeze
453478

test/net/imap/test_config.rb

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ class ConfigTest < Test::Unit::TestCase
140140

141141
test ".version_defaults are all frozen, and inherit debug from global" do
142142
Config.version_defaults.each do |name, config|
143-
assert [0, Float, Symbol].any? { _1 === name }
143+
assert [0, Float, Rational, Symbol].any? { _1 === name }
144144
assert_kind_of Config, config
145145
assert config.frozen?, "#{name} isn't frozen"
146146
assert config.inherited?(:debug), "#{name} doesn't inherit debug"
@@ -165,14 +165,18 @@ class ConfigTest < Test::Unit::TestCase
165165
end
166166

167167
test ".[] for all x.y versions" do
168-
original = Config[0]
168+
original = Config[0r]
169169
assert_kind_of Config, original
170+
assert_same original, Config[0]
170171
assert_same original, Config[0.0]
171172
assert_same original, Config[0.1]
172173
assert_same original, Config[0.2]
173174
assert_same original, Config[0.3]
174175
((0.4r..FUTURE_VERSION.to_r) % 0.1r).each do |version|
175-
assert_kind_of Config, Config[version.to_f]
176+
config = Config[version]
177+
assert_kind_of Config, config
178+
assert_same config, Config[version.to_f]
179+
assert_same config, Config[version.to_f.to_r]
176180
end
177181
end
178182

@@ -186,6 +190,8 @@ class ConfigTest < Test::Unit::TestCase
186190

187191
test ".[] key errors" do
188192
assert_raise(KeyError) do Config[:nonexistent] end
193+
assert_raise(KeyError) do Config["nonexistent"] end
194+
assert_raise(KeyError) do Config["0.01"] end
189195
end
190196

191197
test ".[] with symbol names" do
@@ -195,6 +201,52 @@ class ConfigTest < Test::Unit::TestCase
195201
assert_same Config[FUTURE_VERSION], Config[:future]
196202
end
197203

204+
test ".[] with string names" do
205+
assert_same Config[:original], Config["original"]
206+
assert_same Config[:current], Config["current"]
207+
assert_same Config[0.4r], Config["0.4.11"]
208+
assert_same Config[0.5r], Config["0.5.6"]
209+
assert_same Config[:current], Config[Net::IMAP::VERSION]
210+
end
211+
212+
test ".[] with object responding to to_sym, to_r, or to_f" do
213+
# responds to none of the methods
214+
duck = Object.new
215+
assert_raise TypeError do Config[duck] end
216+
217+
# to_sym
218+
duck = Object.new
219+
def duck.to_sym; :current end
220+
assert_same Config[:current], Config[duck]
221+
222+
# to_r
223+
duck = Object.new
224+
def duck.to_r; 0.6r end
225+
assert_same Config[0.6r], Config[duck]
226+
227+
# to_f
228+
duck = Object.new
229+
def duck.to_f; 0.4 end
230+
assert_same Config[0.4], Config[duck]
231+
232+
# prefer to_r over to_f
233+
def duck.to_r; 0.5r end
234+
assert_same Config[0.5r], Config[duck]
235+
236+
# prefer to_sym over to_r
237+
def duck.to_sym; :original end
238+
assert_same Config[:original], Config[duck]
239+
240+
# keeps trying if to_sym finds nothing
241+
duck = Object.new
242+
def duck.to_sym; :nope end
243+
def duck.to_f; 0.5 end
244+
assert_same Config[0.5], Config[duck]
245+
# keeps trying if to_sym and to_r both find nothing
246+
def duck.to_r; 1/11111 end
247+
assert_same Config[0.5], Config[duck]
248+
end
249+
198250
test ".[] with a hash" do
199251
config = Config[{responses_without_block: :raise, sasl_ir: false}]
200252
assert config.frozen?

0 commit comments

Comments
 (0)