Skip to content

Commit d839268

Browse files
authored
🔀 Merge pull request #429 from ruby/rational-config-versions
♻️ Rational config versions
2 parents 9b3317b + c7732e6 commit d839268

File tree

2 files changed

+95
-19
lines changed

2 files changed

+95
-19
lines changed

lib/net/imap/config.rb

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,25 @@ def self.default; @default end
131131
def self.global; @global if defined?(@global) end
132132

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

137154
# :call-seq:
138155
# Net::IMAP::Config[number] -> versioned config
@@ -155,18 +172,17 @@ def self.[](config)
155172
elsif config.nil? && global.nil? then nil
156173
elsif config.respond_to?(:to_hash) then new(global, **config).freeze
157174
else
158-
version_defaults.fetch(config) do
175+
version_defaults[config] or
159176
case config
160177
when Numeric
161178
raise RangeError, "unknown config version: %p" % [config]
162-
when Symbol
179+
when String, Symbol
163180
raise KeyError, "unknown config name: %p" % [config]
164181
else
165182
raise TypeError, "no implicit conversion of %s to %s" % [
166183
config.class, Config
167184
]
168185
end
169-
end
170186
end
171187
end
172188

@@ -439,44 +455,52 @@ def defaults_hash
439455

440456
version_defaults[:default] = Config[default.send(:defaults_hash)]
441457

442-
version_defaults[0] = Config[:default].dup.update(
458+
version_defaults[0r] = Config[:default].dup.update(
443459
sasl_ir: false,
444460
responses_without_block: :silence_deprecation_warning,
445461
enforce_logindisabled: false,
446462
parser_use_deprecated_uidplus_data: true,
447463
parser_max_deprecated_uidplus_data_size: 10_000,
448464
).freeze
449-
version_defaults[0.0] = Config[0]
450-
version_defaults[0.1] = Config[0]
451-
version_defaults[0.2] = Config[0]
452-
version_defaults[0.3] = Config[0]
465+
version_defaults[0.0r] = Config[0r]
466+
version_defaults[0.1r] = Config[0r]
467+
version_defaults[0.2r] = Config[0r]
468+
version_defaults[0.3r] = Config[0r]
453469

454-
version_defaults[0.4] = Config[0.3].dup.update(
470+
version_defaults[0.4r] = Config[0.3r].dup.update(
455471
sasl_ir: true,
456472
parser_max_deprecated_uidplus_data_size: 1000,
457473
).freeze
458474

459-
version_defaults[0.5] = Config[0.4].dup.update(
475+
version_defaults[0.5r] = Config[0.4r].dup.update(
460476
enforce_logindisabled: true,
461477
responses_without_block: :warn,
462478
parser_use_deprecated_uidplus_data: :up_to_max_size,
463479
parser_max_deprecated_uidplus_data_size: 100,
464480
).freeze
465481

466-
version_defaults[0.6] = Config[0.5].dup.update(
482+
version_defaults[0.6r] = Config[0.5r].dup.update(
467483
responses_without_block: :frozen_dup,
468484
parser_use_deprecated_uidplus_data: false,
469485
parser_max_deprecated_uidplus_data_size: 0,
470486
).freeze
471487

472-
version_defaults[0.7] = Config[0.6].dup.update(
488+
version_defaults[0.7r] = Config[0.6r].dup.update(
473489
).freeze
474490

475-
current = VERSION.to_f
491+
# Safe conversions one way only:
492+
# 0.6r.to_f == 0.6 # => true
493+
# 0.6 .to_r == 0.6r # => false
494+
version_defaults.to_a.each do |k, v|
495+
next unless k in Rational
496+
version_defaults[k.to_f] = v
497+
end
498+
499+
current = VERSION.to_r
476500
version_defaults[:original] = Config[0]
477501
version_defaults[:current] = Config[current]
478-
version_defaults[:next] = Config[current + 0.1]
479-
version_defaults[:future] = Config[current + 0.2]
502+
version_defaults[:next] = Config[current + 0.1r]
503+
version_defaults[:future] = Config[current + 0.2r]
480504

481505
version_defaults.freeze
482506

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
220+
assert_same Config[:current], Config[duck]
221+
222+
# to_r
223+
duck = Object.new
224+
def duck.to_r = 0.6r
225+
assert_same Config[0.6r], Config[duck]
226+
227+
# to_f
228+
duck = Object.new
229+
def duck.to_f = 0.4
230+
assert_same Config[0.4], Config[duck]
231+
232+
# prefer to_r over to_f
233+
def duck.to_r = 0.5r
234+
assert_same Config[0.5r], Config[duck]
235+
236+
# prefer to_sym over to_r
237+
def duck.to_sym = :original
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
243+
def duck.to_f = 0.5
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
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)