net-imap 0.5.9 → 0.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9a31378c34762136e5fc341801a0b4073157dc6c35df6aae3254d3f7b90a07b7
4
- data.tar.gz: cbf787e39ecfde5a7af0061baba54eae3d7b12077679854ea62b335fc9b48798
3
+ metadata.gz: ffcec5e3cc42d7f72c63520110ec442f760c35472401de495001f420cfa059c5
4
+ data.tar.gz: aedd703997fc651cb6c67635a3d20a4159720c6e1bda41c07322f2bd67a627d6
5
5
  SHA512:
6
- metadata.gz: fd0c8a34fa212bb42a55e943bf8184c614256b52da4ac13bdddb48f74c8517f5c7b72addb2153af42d76f2a8bcf42627ceb34e874b7842d8a6dfd542ba577578
7
- data.tar.gz: 91dd4d1d35582abb9e4fd0df64c63ee5c9320f3404d548bff0a4023e32dc9a6ddd6b90cd1cc221e637a4719f3cc5699d57cc337ea17a6454b3aa7d7be9ffb423
6
+ metadata.gz: 1fa95302f29607d152b991535a9d14cda5dae1a1fce06b1d831dd2b09f69b2fd845367e0ebed73dd4e79d04c064a801e59865e76947060a4811304728458f3c7
7
+ data.tar.gz: 2dc9a56758d6f370affc9540a8ca26b8ad866ca5ef0e5b9bc2c30ff6257e3461e5ed23173e1c5e98a2e95c1e0f0a844c96f9e34d41151e9196ab513ac6987d33
data/.document ADDED
@@ -0,0 +1,3 @@
1
+ *.rdoc
2
+ *.md
3
+ lib
data/.rdoc_options ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ charset: UTF-8
3
+ main_page: README.md
4
+ markup: rdoc
5
+ title: net-imap # rake task override's title to add the version number
6
+ op_dir: doc
7
+ # vim:ft=yaml
data/Gemfile CHANGED
@@ -4,21 +4,23 @@ source "https://rubygems.org"
4
4
 
5
5
  gemspec
6
6
 
7
- gem "digest"
7
+ # gem "digest" # not included as a workaround for #576
8
8
  gem "strscan"
9
9
  gem "base64"
10
+ gem "psych", ">= 5.3.0" # 5.2.5 for Data serialization, 5.3.0 for TruffleRuby
10
11
 
11
12
  gem "irb"
12
13
  gem "rake"
13
- gem "rdoc"
14
+ gem "rdoc", ">= 7.2.0"
14
15
  gem "test-unit"
15
16
  gem "test-unit-ruby-core", git: "https://github.com/ruby/test-unit-ruby-core"
16
17
 
17
18
  gem "benchmark", require: false
18
19
  gem "benchmark-driver", require: false
20
+ gem "vernier", require: false, platform: :mri
19
21
 
20
22
  group :test do
21
- gem "simplecov", require: false
22
- gem "simplecov-html", require: false
23
- gem "simplecov-json", require: false
23
+ gem "simplecov", require: false, platforms: %i[mri windows]
24
+ gem "simplecov-html", require: false, platforms: %i[mri windows]
25
+ gem "simplecov-json", require: false, platforms: %i[mri windows]
24
26
  end
data/README.md CHANGED
@@ -61,9 +61,9 @@ imap.search(["BEFORE", "30-Apr-2003", "SINCE", "1-Apr-2003"]).each do |message_i
61
61
  else
62
62
  imap.copy(message_id, "Mail/sent-apr03")
63
63
  imap.store(message_id, "+FLAGS", [:Deleted])
64
+ imap.expunge
64
65
  end
65
66
  end
66
- imap.expunge
67
67
  ```
68
68
 
69
69
  ## Development
@@ -3,7 +3,8 @@
3
3
  require "date"
4
4
 
5
5
  require_relative "errors"
6
- require_relative "data_lite"
6
+
7
+ # :enddoc:
7
8
 
8
9
  module Net
9
10
  class IMAP < Protocol
@@ -26,6 +27,7 @@ module Net
26
27
  end
27
28
  when Time, Date, DateTime
28
29
  when Symbol
30
+ Flag.validate(data)
29
31
  else
30
32
  data.validate
31
33
  end
@@ -46,7 +48,7 @@ module Net
46
48
  when Date
47
49
  send_date_data(data)
48
50
  when Symbol
49
- send_symbol_data(data)
51
+ Flag[data].send_data(self, tag)
50
52
  else
51
53
  data.send_data(self, tag)
52
54
  end
@@ -78,9 +80,23 @@ module Net
78
80
  put_string('"' + str.gsub(/["\\]/, "\\\\\\&") + '"')
79
81
  end
80
82
 
81
- def send_literal(str, tag = nil)
83
+ def send_binary_literal(*, **) = send_literal(*, **, binary: true)
84
+
85
+ # `non_sync` is an optional tri-state flag:
86
+ # * `true` -> Force non-synchronizing `LITERAL+`/`LITERAL-` behavior.
87
+ # TODO: raise or warn when capabilities don't allow non_sync.
88
+ # * `false` -> Force normal synchronizing literal behavior.
89
+ # * `nil` -> (default) Currently behaves like `false` (will be dynamic).
90
+ def send_literal(str, tag = nil, binary: false, non_sync: nil)
82
91
  synchronize do
83
- put_string("{" + str.bytesize.to_s + "}" + CRLF)
92
+ non_sync = non_sync_literal?(str.bytesize) if non_sync.nil?
93
+ prefix = "~" if binary
94
+ plus = "+" if non_sync
95
+ put_string("#{prefix}{#{str.bytesize}#{plus}}\r\n")
96
+ if non_sync
97
+ put_string(str)
98
+ return
99
+ end
84
100
  @continued_command_tag = tag
85
101
  @continuation_request_exception = nil
86
102
  begin
@@ -95,6 +111,13 @@ module Net
95
111
  end
96
112
  end
97
113
 
114
+ def non_sync_literal?(bytesize)
115
+ capabilities_cached? &&
116
+ bytesize <= config.max_non_synchronizing_literal &&
117
+ (capable?("LITERAL+") ||
118
+ bytesize <= 4096 && (capable?("IMAP4rev2") || capable?("LITERAL-")))
119
+ end
120
+
98
121
  def send_number_data(num)
99
122
  put_string(num.to_s)
100
123
  end
@@ -116,11 +139,13 @@ module Net
116
139
  def send_date_data(date) put_string Net::IMAP.encode_date(date) end
117
140
  def send_time_data(time) put_string Net::IMAP.encode_time(time) end
118
141
 
119
- def send_symbol_data(symbol)
120
- put_string("\\" + symbol.to_s)
121
- end
122
-
123
142
  CommandData = Data.define(:data) do # :nodoc:
143
+ def self.validate(...)
144
+ data = new(...)
145
+ data.validate
146
+ data
147
+ end
148
+
124
149
  def send_data(imap, tag)
125
150
  raise NoMethodError, "#{self.class} must implement #{__method__}"
126
151
  end
@@ -129,15 +154,109 @@ module Net
129
154
  end
130
155
  end
131
156
 
157
+ # Represents IMAP +text+ data, which may contain any 7-bit ASCII character,
158
+ # except for +NULL+, +CR+, or +LF+. +text+ is extended to allow any
159
+ # multibyte +UTF-8+ character when either +UTF8=ACCEPT+ or +IMAP4rev2+ have
160
+ # been enabled, or when the server supports only +IMAP4rev2+ and not earlier
161
+ # IMAP revisions, or when the server advertises +UTF8=ONLY+.
162
+ #
163
+ # NOTE: The current implementation does not validate whether the connection
164
+ # currently supports UTF-8. Future versions may change.
165
+ #
166
+ # The string's bytes must be valid ASCII or valid UTF-8. The string's
167
+ # reported encoding is ignored, but the string is _not_ transcoded.
168
+ class RawText < CommandData # :nodoc:
169
+ def initialize(data:)
170
+ data = String(data.to_str)
171
+ data = if data.encoding in Encoding::ASCII | Encoding::UTF_8
172
+ -data
173
+ elsif data.ascii_only?
174
+ -(data.dup.force_encoding("ASCII"))
175
+ else
176
+ -(data.dup.force_encoding("UTF-8"))
177
+ end
178
+ super
179
+ validate
180
+ end
181
+
182
+ def validate
183
+ if data.include?("\0")
184
+ raise DataFormatError, "NULL byte must be binary literal encoded"
185
+ elsif !data.valid_encoding?
186
+ raise DataFormatError, "invalid UTF-8 must be literal encoded"
187
+ elsif /[\r\n]/.match?(data)
188
+ raise DataFormatError, "CR and LF bytes must be literal encoded"
189
+ end
190
+ end
191
+
192
+ def ascii_only? = data.ascii_only?
193
+
194
+ def send_data(imap, tag) = imap.__send__(:put_string, data)
195
+ end
196
+
132
197
  class RawData < CommandData # :nodoc:
133
- def send_data(imap, tag)
134
- imap.__send__(:put_string, data)
198
+ def initialize(data:)
199
+ data = split_parts(data)
200
+ super
201
+ validate
202
+ end
203
+
204
+ def send_data(imap, tag) = data.each do _1.send_data(imap, tag) end
205
+
206
+ def validate
207
+ return unless data.last in RawText(data: text)
208
+ if text.rindex(/~?\{[1-9]\d*\+?\}\z/n)
209
+ raise DataFormatError, "RawData cannot end with literal continuation"
210
+ end
211
+ end
212
+
213
+ private
214
+
215
+ def split_parts(data)
216
+ data = data.b # dups and ensures BINARY encoding
217
+ parts = []
218
+ while data.match(/(~)?\{(0|[1-9]\d*)(\+)?\}\r\n/n)
219
+ text, binary, bytesize, non_sync, data = $`, !!$1, $2, !!$3, $'
220
+ bytesize = NumValidator.coerce_number64 bytesize
221
+ parts << RawText[text] unless text.empty?
222
+ parts << extract_literal(data, binary:, bytesize:, non_sync:)
223
+ data.bytesplice(0, bytesize, "")
224
+ end
225
+ parts << RawText[data] unless data.empty?
226
+ parts
227
+ end
228
+
229
+ def extract_literal(data, binary:, bytesize:, non_sync:)
230
+ if data.bytesize < bytesize
231
+ raise DataFormatError, "Too few bytes in string for literal, " \
232
+ "expected: %s, remaining: %s" % [bytesize, data.bytesize]
233
+ end
234
+ literal = data.byteslice(0, bytesize)
235
+ (binary ? Literal8 : Literal).new(data: literal, non_sync:)
135
236
  end
136
237
  end
137
238
 
138
239
  class Atom < CommandData # :nodoc:
240
+ def initialize(**)
241
+ super
242
+ validate
243
+ end
244
+
245
+ def validate
246
+ data.to_s.ascii_only? \
247
+ or raise DataFormatError, "#{self.class} must be ASCII only"
248
+ data.match?(ResponseParser::Patterns::ATOM_SPECIALS) \
249
+ and raise DataFormatError, "#{self.class} must not contain atom-specials"
250
+ end
251
+
139
252
  def send_data(imap, tag)
140
- imap.__send__(:put_string, data)
253
+ imap.__send__(:put_string, data.to_s)
254
+ end
255
+ end
256
+
257
+ class Flag < Atom # :nodoc:
258
+ def send_data(imap, tag)
259
+ imap.__send__(:put_string, "\\#{data}")
141
260
  end
142
261
  end
143
262
 
@@ -147,9 +266,39 @@ module Net
147
266
  end
148
267
  end
149
268
 
150
- class Literal < CommandData # :nodoc:
269
+ class Literal < Data.define(:data, :non_sync) # :nodoc:
270
+ def self.validate(...)
271
+ data = new(...)
272
+ data.validate
273
+ data
274
+ end
275
+
276
+ def initialize(data:, non_sync: nil)
277
+ data = -String(data.to_str).b or
278
+ raise DataFormatError, "#{self.class} expects string input"
279
+ super
280
+ validate
281
+ end
282
+
283
+ def bytesize = data.bytesize
284
+
285
+ def validate
286
+ if data.include?("\0")
287
+ raise DataFormatError, "NULL byte not allowed in #{self.class}. " \
288
+ "Use #{Literal8} or a null-safe encoding."
289
+ end
290
+ end
291
+
292
+ def send_data(imap, tag)
293
+ imap.__send__(:send_literal, data, tag, non_sync:)
294
+ end
295
+ end
296
+
297
+ class Literal8 < Literal # :nodoc:
298
+ def validate = nil # all bytes are okay
299
+
151
300
  def send_data(imap, tag)
152
- imap.__send__(:send_literal, data, tag)
301
+ imap.__send__(:send_binary_literal, data, tag, non_sync:)
153
302
  end
154
303
  end
155
304
 
@@ -185,73 +334,6 @@ module Net
185
334
  end
186
335
  end
187
336
 
188
- # *DEPRECATED*. Replaced by SequenceSet.
189
- class MessageSet < CommandData # :nodoc:
190
- def send_data(imap, tag)
191
- imap.__send__(:put_string, format_internal(data))
192
- end
193
-
194
- def validate
195
- validate_internal(data)
196
- end
197
-
198
- private
199
-
200
- def initialize(data:)
201
- super
202
- warn("DEPRECATED: #{MessageSet} should be replaced with #{SequenceSet}.",
203
- uplevel: 1, category: :deprecated)
204
- begin
205
- # to ensure the input works with SequenceSet, too
206
- SequenceSet.new(data)
207
- rescue
208
- warn "MessageSet input is incompatible with SequenceSet: [%s] %s" % [
209
- $!.class, $!.message
210
- ]
211
- end
212
- end
213
-
214
- def format_internal(data)
215
- case data
216
- when "*"
217
- return data
218
- when Integer
219
- if data == -1
220
- return "*"
221
- else
222
- return data.to_s
223
- end
224
- when Range
225
- return format_internal(data.first) +
226
- ":" + format_internal(data.last)
227
- when Array
228
- return data.collect {|i| format_internal(i)}.join(",")
229
- when ThreadMember
230
- return data.seqno.to_s +
231
- ":" + data.children.collect {|i| format_internal(i).join(",")}
232
- end
233
- end
234
-
235
- def validate_internal(data)
236
- case data
237
- when "*"
238
- when Integer
239
- NumValidator.ensure_nz_number(data)
240
- when Range
241
- when Array
242
- data.each do |i|
243
- validate_internal(i)
244
- end
245
- when ThreadMember
246
- data.children.each do |i|
247
- validate_internal(i)
248
- end
249
- else
250
- raise DataFormatError, data.inspect
251
- end
252
- end
253
- end
254
-
255
337
  class ClientID < CommandData # :nodoc:
256
338
 
257
339
  def send_data(imap, tag)
@@ -289,6 +371,14 @@ module Net
289
371
 
290
372
  module_function
291
373
 
374
+ def literal_or_literal8(input, name: "argument")
375
+ return input if input in Literal | Literal8
376
+ data = String.try_convert(input) \
377
+ or raise TypeError, "expected #{name} to be String, got #{input.class}"
378
+ type = data.include?("\0") ? Literal8 : Literal
379
+ type.new(data:)
380
+ end
381
+
292
382
  # Allows symbols in addition to strings
293
383
  def valid_string?(str)
294
384
  str.is_a?(Symbol) || str.respond_to?(:to_str)
@@ -27,24 +27,23 @@ module Net
27
27
 
28
28
  def self.attr_accessor(name) # :nodoc: internal API
29
29
  name = name.to_sym
30
+ raise ArgumentError, "already defined #{name}" if attributes.include?(name)
31
+ attributes << name
30
32
  def_delegators :data, name, :"#{name}="
31
33
  end
32
34
 
33
- def self.attributes
34
- instance_methods.grep(/=\z/).map { _1.to_s.delete_suffix("=").to_sym }
35
- end
36
- private_class_method :attributes
35
+ # An array of Config attribute names
36
+ singleton_class.attr_reader :attributes
37
+ @attributes = []
37
38
 
38
39
  def self.struct # :nodoc: internal API
39
- unless defined?(self::Struct)
40
- const_set :Struct, Struct.new(*attributes)
41
- end
42
- self::Struct
40
+ attributes.freeze
41
+ Struct.new(*attributes)
43
42
  end
44
43
 
45
44
  def initialize # :notnew:
46
45
  super()
47
- @data = AttrAccessors.struct.new
46
+ @data = Config::Struct.new
48
47
  end
49
48
 
50
49
  # Freezes the internal attributes struct, in addition to +self+.
@@ -54,9 +54,72 @@ module Net
54
54
  # Creates a new config, which inherits from +self+.
55
55
  def new(**attrs) self.class.new(self, **attrs) end
56
56
 
57
+ # :call-seq:
58
+ # inherited?(attr) -> true or false
59
+ # inherited?(*attrs) -> true or false
60
+ # inherited? -> true or false
61
+ #
57
62
  # Returns +true+ if +attr+ is inherited from #parent and not overridden
58
63
  # by this config.
59
- def inherited?(attr) data[attr] == INHERITED end
64
+ #
65
+ # When multiple +attrs+ are given, returns +true+ if *all* of them are
66
+ # inherited, or +false+ if any of them are overriden. When no +attrs+
67
+ # are given, returns +true+ if *all* attributes are inherited, or
68
+ # +false+ if any attribute is overriden.
69
+ #
70
+ # Related: #overrides?
71
+ def inherited?(*attrs)
72
+ attrs = data.members if attrs.empty?
73
+ attrs.all? { data[_1] == INHERITED }
74
+ end
75
+
76
+ # :call-seq:
77
+ # inherits_defaults?(*attrs) -> true | Rational | nil | false
78
+ #
79
+ # Returns whether all +attrs+ are inherited from a default config.
80
+ # When no +attrs+ are given, returns whether *all* attributes are
81
+ # inherited from a default config.
82
+ #
83
+ # Returns +true+ when all attributes inherit from Config.default, the
84
+ # version number (as a Rational) when all attributes inherit from a
85
+ # versioned default (see Config@Versioned+defaults), +nil+ if any
86
+ # attributes inherit from Config.global overrides (but not from
87
+ # non-global ancestors), or +false+ when any attributes have been
88
+ # overridden by +self+ or an ancestor (besides global or default
89
+ # configs),
90
+ #
91
+ # Related: #overrides?
92
+ def inherits_defaults?(*attrs)
93
+ if equal?(Config.default)
94
+ true
95
+ elsif equal?(Config.global)
96
+ true if inherited?(*attrs)
97
+ elsif (v = AttrVersionDefaults::VERSIONS.find { equal? Config[_1] })
98
+ attrs = DEFAULT_TO_INHERIT if attrs.empty?
99
+ attrs &= DEFAULT_TO_INHERIT
100
+ (attrs.empty? || parent.inherits_defaults?(*attrs)) && v
101
+ else
102
+ inherited?(*attrs) && parent.inherits_defaults?(*attrs)
103
+ end
104
+ end
105
+
106
+ # :call-seq:
107
+ # overrides?(attr) -> true or false
108
+ # overrides?(*attrs) -> true or false
109
+ # overrides? -> true or false
110
+ #
111
+ # Returns +true+ if +attr+ is defined on this config and not inherited
112
+ # from #parent.
113
+ #
114
+ # When multiple +attrs+ are given, returns +true+ if
115
+ # *any* of them are defined on +self+. When no +attrs+ are given,
116
+ # returns +true+ if *any* attribute is overriden.
117
+ #
118
+ # Related: #inherited?
119
+ def overrides?(*attrs)
120
+ attrs = data.members if attrs.empty?
121
+ attrs.any? { data[_1] != INHERITED }
122
+ end
60
123
 
61
124
  # :call-seq:
62
125
  # reset -> self
@@ -28,10 +28,22 @@ module Net
28
28
  end
29
29
  private_class_method :included
30
30
 
31
- if defined?(Ractor.make_shareable)
32
- def self.safe(...) Ractor.make_shareable nil.instance_eval(...).freeze end
31
+ if defined?(Ractor.shareable_proc)
32
+ def self.safe(&b)
33
+ case obj = b.call
34
+ when Proc
35
+ Ractor.shareable_proc(&obj)
36
+ else
37
+ Ractor.make_shareable obj
38
+ end
39
+ end
40
+ elsif defined?(Ractor.make_shareable)
41
+ def self.safe(&b)
42
+ obj = nil.instance_eval(&b).freeze
43
+ Ractor.make_shareable obj
44
+ end
33
45
  else
34
- def self.safe(...) nil.instance_eval(...).freeze end
46
+ def self.safe(&b) nil.instance_eval(&b).freeze end
35
47
  end
36
48
  private_class_method :safe
37
49
 
@@ -48,10 +60,10 @@ module Net
48
60
  NilOrInteger = safe{->val { Integer val unless val.nil? }}
49
61
 
50
62
  Enum = ->(*enum) {
51
- enum = safe{enum}
52
- expected = -"one of #{enum.map(&:inspect).join(", ")}"
63
+ safe_enum = safe{enum}
64
+ expected = -"one of #{safe_enum.map(&:inspect).join(", ")}"
53
65
  safe{->val {
54
- return val if enum.include?(val)
66
+ return val if safe_enum.include?(val)
55
67
  raise ArgumentError, "expected %s, got %p" % [expected, val]
56
68
  }}
57
69
  }
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+
5
+ module Net
6
+ class IMAP
7
+ class Config
8
+ # >>>
9
+ # *NOTE:* This module is an internal implementation detail, with no
10
+ # guarantee of backward compatibility.
11
+ #
12
+ # Adds a +defaults+ parameter to +attr_accessor+, which is used to compile
13
+ # Config.version_defaults.
14
+ module AttrVersionDefaults
15
+ # The <tt>x.y</tt> part of Net::IMAP::VERSION, as a Rational number.
16
+ CURRENT_VERSION = VERSION.to_r
17
+
18
+ # The config version used for <tt>Config[:next]</tt>.
19
+ NEXT_VERSION = CURRENT_VERSION + 0.1r
20
+
21
+ # The config version used for <tt>Config[:future]</tt>.
22
+ FUTURE_VERSION = 1.0r
23
+
24
+ VERSIONS = ((0.0r..FUTURE_VERSION) % 0.1r).to_a.freeze
25
+
26
+ # See Config.version_defaults.
27
+ singleton_class.attr_reader :version_defaults
28
+
29
+ @version_defaults = Hash.new {|h, k|
30
+ # NOTE: String responds to both so the order is significant.
31
+ # And ignore non-numeric conversion to zero, because: "wat!?".to_r == 0
32
+ (h.fetch(k.to_r, nil) || h.fetch(k.to_f, nil) if k.is_a?(Numeric)) ||
33
+ (h.fetch(k.to_sym, nil) if k.respond_to?(:to_sym)) ||
34
+ (h.fetch(k.to_r, nil) if k.respond_to?(:to_r) && k.to_r != 0r) ||
35
+ (h.fetch(k.to_f, nil) if k.respond_to?(:to_f) && k.to_f != 0.0)
36
+ }
37
+
38
+ # :stopdoc: internal APIs only
39
+
40
+ def attr_accessor(name, defaults: nil, default: (unset = true), **kw)
41
+ unless unset
42
+ version = DEFAULT_TO_INHERIT.include?(name) ? nil : 0.0r
43
+ defaults = { version => default }
44
+ end
45
+ defaults&.each_pair do |version, default|
46
+ AttrVersionDefaults.version_defaults[version] ||= {}
47
+ AttrVersionDefaults.version_defaults[version][name] = default
48
+ end
49
+ super(name, **kw)
50
+ end
51
+
52
+ def self.compile_default!
53
+ raise "Config.default already compiled" if Config.default
54
+ default = VERSIONS.select { _1 <= CURRENT_VERSION }
55
+ .filter_map { version_defaults[_1] }
56
+ .prepend(version_defaults.delete(nil))
57
+ .inject(&:merge)
58
+ Config.new(**default).freeze
59
+ end
60
+
61
+ def self.compile_version_defaults!
62
+ version_defaults[0.0r] = Config[version_defaults.fetch(0.0r)]
63
+
64
+ VERSIONS.each_cons(2) do |prior, version|
65
+ updates = version_defaults[version]
66
+ version_defaults[version] = version_defaults[prior]
67
+ .then { updates ? _1.dup.update(**updates).freeze : _1 }
68
+ end
69
+
70
+ # Safe conversions one way only:
71
+ # 0.6r.to_f == 0.6 # => true
72
+ # 0.6 .to_r == 0.6r # => false
73
+ version_defaults.to_a.each do |k, v|
74
+ next unless k in Rational
75
+ version_defaults[k.to_f] = v
76
+ end
77
+
78
+ version_defaults[:original] = Config[0.0r]
79
+ version_defaults[:current] = Config[CURRENT_VERSION]
80
+ version_defaults[:default] = Config[CURRENT_VERSION]
81
+ version_defaults[:next] = Config[NEXT_VERSION]
82
+ version_defaults[:future] = Config[FUTURE_VERSION]
83
+
84
+ version_defaults.freeze
85
+ end
86
+
87
+ end
88
+ end
89
+ end
90
+ end