Skip to content

Commit 7d7d424

Browse files
committed
♻️ Drop ruby 3.1 Data polyfill
Ruby 3.1 support has already been dropped and this code was only used by ruby 3.1, so this is not a breaking change. For now, we're keeping `Net::IMAP::Data` to support YAML encoding.
1 parent cf3d1f5 commit 7d7d424

File tree

1 file changed

+7
-190
lines changed

1 file changed

+7
-190
lines changed

lib/net/imap/data_lite.rb

Lines changed: 7 additions & 190 deletions
Original file line numberDiff line numberDiff line change
@@ -27,200 +27,17 @@
2727

2828
module Net
2929
class IMAP
30-
data_or_object = RUBY_VERSION >= "3.2.0" ? ::Data : Object
31-
class DataLite < data_or_object
30+
# DataLite subclasses ruby's +Data+ class and is aliased as Net::IMAP::Data,
31+
# so that code using it won't need to be updated when it is removed. It
32+
# adds support for yaml encoding. When psych adds support for Data,
33+
# DataLite _will_ be removed.
34+
#
35+
# Previously, DataLite served as a reimplementation of +Data+ for ruby 3.1.
36+
class DataLite < ::Data
3237
def encode_with(coder) coder.map = to_h.transform_keys(&:to_s) end
3338
def init_with(coder) initialize(**coder.map.transform_keys(&:to_sym)) end
3439
end
3540

3641
Data = DataLite
3742
end
3843
end
39-
40-
# :nocov:
41-
# Need to skip test coverage for the rest, because it isn't loaded by ruby 3.2+.
42-
return if RUBY_VERSION >= "3.2.0"
43-
44-
module Net
45-
class IMAP
46-
# DataLite is a temporary substitute for ruby 3.2's +Data+ class. DataLite
47-
# is aliased as Net::IMAP::Data, so that code using it won't need to be
48-
# updated when it is removed.
49-
#
50-
# See {ruby 3.2's documentation for Data}[https://docs.ruby-lang.org/en/3.2/Data.html].
51-
#
52-
# [When running ruby 3.1]
53-
# This class reimplements the API for ruby 3.2's +Data+, and should be
54-
# compatible for nearly all use-cases. This reimplementation <em>will be
55-
# removed</em> in +net-imap+ 0.6, when support for ruby 3.1 is dropped.
56-
#
57-
# _NOTE:_ +net-imap+ no longer supports ruby versions prior to 3.1.
58-
# [When running ruby >= 3.2]
59-
# This class inherits from +Data+ and _only_ defines the methods needed
60-
# for YAML serialization. This will be dropped when +psych+ adds support
61-
# for +Data+.
62-
#
63-
# Some of the code in this class was copied or adapted from the
64-
# {polyfill-data gem}[https://rubygems.org/gems/polyfill-data], by Jim Gay
65-
# and Joel Drapper, under the MIT license terms.
66-
class DataLite
67-
singleton_class.undef_method :new
68-
69-
TYPE_ERROR = "%p is not a symbol nor a string"
70-
ATTRSET_ERROR = "invalid data member: %p"
71-
DUP_ERROR = "duplicate member: %p"
72-
ARITY_ERROR = "wrong number of arguments (given %d, expected %s)"
73-
private_constant :TYPE_ERROR, :ATTRSET_ERROR, :DUP_ERROR, :ARITY_ERROR
74-
75-
# Defines a new Data class.
76-
#
77-
# _NOTE:_ Unlike ruby 3.2's +Data.define+, DataLite.define only supports
78-
# member names which are valid local variable names. Member names can't
79-
# be keywords (e.g: +next+ or +class+) or start with capital letters, "@",
80-
# etc.
81-
def self.define(*args, &block)
82-
members = args.each_with_object({}) do |arg, members|
83-
arg = arg.to_str unless arg in Symbol | String if arg.respond_to?(:to_str)
84-
arg = arg.to_sym if arg in String
85-
arg in Symbol or raise TypeError, TYPE_ERROR % [arg]
86-
arg in %r{=} and raise ArgumentError, ATTRSET_ERROR % [arg]
87-
members.key?(arg) and raise ArgumentError, DUP_ERROR % [arg]
88-
members[arg] = true
89-
end
90-
members = members.keys.freeze
91-
92-
klass = ::Class.new(self)
93-
94-
klass.singleton_class.undef_method :define
95-
klass.define_singleton_method(:members) { members }
96-
97-
def klass.new(*args, **kwargs, &block)
98-
if kwargs.size.positive?
99-
if args.size.positive?
100-
raise ArgumentError, ARITY_ERROR % [args.size, 0]
101-
end
102-
elsif members.size < args.size
103-
expected = members.size.zero? ? 0 : 0..members.size
104-
raise ArgumentError, ARITY_ERROR % [args.size, expected]
105-
else
106-
kwargs = Hash[members.take(args.size).zip(args)]
107-
end
108-
allocate.tap do |instance|
109-
instance.__send__(:initialize, **kwargs, &block)
110-
end.freeze
111-
end
112-
113-
klass.singleton_class.alias_method :[], :new
114-
klass.attr_reader(*members)
115-
116-
# Dynamically defined initializer methods are in an included module,
117-
# rather than directly on DataLite (like in ruby 3.2+):
118-
# * simpler to handle required kwarg ArgumentErrors
119-
# * easier to ensure consistent ivar assignment order (object shape)
120-
# * faster than instance_variable_set
121-
klass.include(Module.new do
122-
if members.any?
123-
kwargs = members.map{"#{_1.name}:"}.join(", ")
124-
params = members.map(&:name).join(", ")
125-
ivars = members.map{"@#{_1.name}"}.join(", ")
126-
attrs = members.map{"attrs[:#{_1.name}]"}.join(", ")
127-
module_eval <<~RUBY, __FILE__, __LINE__ + 1
128-
protected
129-
def initialize(#{kwargs}) #{ivars} = #{params}; freeze end
130-
def marshal_load(attrs) #{ivars} = #{attrs}; freeze end
131-
RUBY
132-
end
133-
end)
134-
135-
klass.module_eval do _1.module_eval(&block) end if block_given?
136-
137-
klass
138-
end
139-
140-
##
141-
# singleton-method: new
142-
# call-seq:
143-
# new(*args) -> instance
144-
# new(**kwargs) -> instance
145-
#
146-
# Constuctor for classes defined with ::define.
147-
#
148-
# Aliased as ::[].
149-
150-
##
151-
# singleton-method: []
152-
# call-seq:
153-
# ::[](*args) -> instance
154-
# ::[](**kwargs) -> instance
155-
#
156-
# Constuctor for classes defined with ::define.
157-
#
158-
# Alias for ::new
159-
160-
##
161-
def members; self.class.members end
162-
def to_h(&block) block ? __to_h__.to_h(&block) : __to_h__ end
163-
def hash; [self.class, __to_h__].hash end
164-
def ==(other) self.class == other.class && to_h == other.to_h end
165-
def eql?(other) self.class == other.class && hash == other.hash end
166-
def deconstruct; __to_h__.values end
167-
168-
def deconstruct_keys(keys)
169-
raise TypeError unless keys.is_a?(Array) || keys.nil?
170-
return __to_h__ if keys&.first.nil?
171-
__to_h__.slice(*keys)
172-
end
173-
174-
def with(**kwargs)
175-
return self if kwargs.empty?
176-
self.class.new(**__to_h__.merge(kwargs))
177-
end
178-
179-
def inspect
180-
__inspect_guard__(self) do |seen|
181-
return "#<data #{self.class}:...>" if seen
182-
attrs = __to_h__.map {|kv| "%s=%p" % kv }.join(", ")
183-
display = ["data", self.class.name, attrs].compact.join(" ")
184-
"#<#{display}>"
185-
end
186-
end
187-
alias_method :to_s, :inspect
188-
189-
private
190-
191-
def initialize_copy(source) super.freeze end
192-
def marshal_dump; __to_h__ end
193-
194-
def __to_h__; Hash[members.map {|m| [m, send(m)] }] end
195-
196-
# Yields +true+ if +obj+ has been seen already, +false+ if it hasn't.
197-
# Marks +obj+ as seen inside the block, so circuler references don't
198-
# recursively trigger a SystemStackError (stack level too deep).
199-
#
200-
# Making circular references inside a Data object _should_ be very
201-
# uncommon, but we'll support them for the sake of completeness.
202-
def __inspect_guard__(obj)
203-
preexisting = Thread.current[:__net_imap_data__inspect__]
204-
Thread.current[:__net_imap_data__inspect__] ||= {}.compare_by_identity
205-
inspect_guard = Thread.current[:__net_imap_data__inspect__]
206-
if inspect_guard.include?(obj)
207-
yield true
208-
else
209-
begin
210-
inspect_guard[obj] = true
211-
yield false
212-
ensure
213-
inspect_guard.delete(obj)
214-
end
215-
end
216-
ensure
217-
unless preexisting.equal?(inspect_guard)
218-
Thread.current[:__net_imap_data__inspect__] = preexisting
219-
end
220-
end
221-
222-
end
223-
224-
end
225-
end
226-
# :nocov:

0 commit comments

Comments
 (0)