diff --git a/lib/net/imap/data_lite.rb b/lib/net/imap/data_lite.rb index 5b96c873..2fafb7c4 100644 --- a/lib/net/imap/data_lite.rb +++ b/lib/net/imap/data_lite.rb @@ -1,34 +1,14 @@ # frozen_string_literal: true -# Some of the code in this file was copied from the polyfill-data gem. -# -# MIT License -# -# Copyright (c) 2023 Jim Gay, Joel Drapper, Nicholas Evans -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - - module Net class IMAP - data_or_object = RUBY_VERSION >= "3.2.0" ? ::Data : Object - class DataLite < data_or_object + # DataLite subclasses ruby's +Data+ class and is aliased as Net::IMAP::Data, + # so that code using it won't need to be updated when it is removed. It + # adds support for yaml encoding. When psych adds support for Data, + # DataLite _will_ be removed. + # + # Previously, DataLite served as a reimplementation of +Data+ for ruby 3.1. + class DataLite < ::Data def encode_with(coder) coder.map = to_h.transform_keys(&:to_s) end def init_with(coder) initialize(**coder.map.transform_keys(&:to_sym)) end end @@ -36,191 +16,3 @@ def init_with(coder) initialize(**coder.map.transform_keys(&:to_sym)) end Data = DataLite end end - -# :nocov: -# Need to skip test coverage for the rest, because it isn't loaded by ruby 3.2+. -return if RUBY_VERSION >= "3.2.0" - -module Net - class IMAP - # DataLite is a temporary substitute for ruby 3.2's +Data+ class. DataLite - # is aliased as Net::IMAP::Data, so that code using it won't need to be - # updated when it is removed. - # - # See {ruby 3.2's documentation for Data}[https://docs.ruby-lang.org/en/3.2/Data.html]. - # - # [When running ruby 3.1] - # This class reimplements the API for ruby 3.2's +Data+, and should be - # compatible for nearly all use-cases. This reimplementation will be - # removed in +net-imap+ 0.6, when support for ruby 3.1 is dropped. - # - # _NOTE:_ +net-imap+ no longer supports ruby versions prior to 3.1. - # [When running ruby >= 3.2] - # This class inherits from +Data+ and _only_ defines the methods needed - # for YAML serialization. This will be dropped when +psych+ adds support - # for +Data+. - # - # Some of the code in this class was copied or adapted from the - # {polyfill-data gem}[https://rubygems.org/gems/polyfill-data], by Jim Gay - # and Joel Drapper, under the MIT license terms. - class DataLite - singleton_class.undef_method :new - - TYPE_ERROR = "%p is not a symbol nor a string" - ATTRSET_ERROR = "invalid data member: %p" - DUP_ERROR = "duplicate member: %p" - ARITY_ERROR = "wrong number of arguments (given %d, expected %s)" - private_constant :TYPE_ERROR, :ATTRSET_ERROR, :DUP_ERROR, :ARITY_ERROR - - # Defines a new Data class. - # - # _NOTE:_ Unlike ruby 3.2's +Data.define+, DataLite.define only supports - # member names which are valid local variable names. Member names can't - # be keywords (e.g: +next+ or +class+) or start with capital letters, "@", - # etc. - def self.define(*args, &block) - members = args.each_with_object({}) do |arg, members| - arg = arg.to_str unless arg in Symbol | String if arg.respond_to?(:to_str) - arg = arg.to_sym if arg in String - arg in Symbol or raise TypeError, TYPE_ERROR % [arg] - arg in %r{=} and raise ArgumentError, ATTRSET_ERROR % [arg] - members.key?(arg) and raise ArgumentError, DUP_ERROR % [arg] - members[arg] = true - end - members = members.keys.freeze - - klass = ::Class.new(self) - - klass.singleton_class.undef_method :define - klass.define_singleton_method(:members) { members } - - def klass.new(*args, **kwargs, &block) - if kwargs.size.positive? - if args.size.positive? - raise ArgumentError, ARITY_ERROR % [args.size, 0] - end - elsif members.size < args.size - expected = members.size.zero? ? 0 : 0..members.size - raise ArgumentError, ARITY_ERROR % [args.size, expected] - else - kwargs = Hash[members.take(args.size).zip(args)] - end - allocate.tap do |instance| - instance.__send__(:initialize, **kwargs, &block) - end.freeze - end - - klass.singleton_class.alias_method :[], :new - klass.attr_reader(*members) - - # Dynamically defined initializer methods are in an included module, - # rather than directly on DataLite (like in ruby 3.2+): - # * simpler to handle required kwarg ArgumentErrors - # * easier to ensure consistent ivar assignment order (object shape) - # * faster than instance_variable_set - klass.include(Module.new do - if members.any? - kwargs = members.map{"#{_1.name}:"}.join(", ") - params = members.map(&:name).join(", ") - ivars = members.map{"@#{_1.name}"}.join(", ") - attrs = members.map{"attrs[:#{_1.name}]"}.join(", ") - module_eval <<~RUBY, __FILE__, __LINE__ + 1 - protected - def initialize(#{kwargs}) #{ivars} = #{params}; freeze end - def marshal_load(attrs) #{ivars} = #{attrs}; freeze end - RUBY - end - end) - - klass.module_eval do _1.module_eval(&block) end if block_given? - - klass - end - - ## - # singleton-method: new - # call-seq: - # new(*args) -> instance - # new(**kwargs) -> instance - # - # Constuctor for classes defined with ::define. - # - # Aliased as ::[]. - - ## - # singleton-method: [] - # call-seq: - # ::[](*args) -> instance - # ::[](**kwargs) -> instance - # - # Constuctor for classes defined with ::define. - # - # Alias for ::new - - ## - def members; self.class.members end - def to_h(&block) block ? __to_h__.to_h(&block) : __to_h__ end - def hash; [self.class, __to_h__].hash end - def ==(other) self.class == other.class && to_h == other.to_h end - def eql?(other) self.class == other.class && hash == other.hash end - def deconstruct; __to_h__.values end - - def deconstruct_keys(keys) - raise TypeError unless keys.is_a?(Array) || keys.nil? - return __to_h__ if keys&.first.nil? - __to_h__.slice(*keys) - end - - def with(**kwargs) - return self if kwargs.empty? - self.class.new(**__to_h__.merge(kwargs)) - end - - def inspect - __inspect_guard__(self) do |seen| - return "#" if seen - attrs = __to_h__.map {|kv| "%s=%p" % kv }.join(", ") - display = ["data", self.class.name, attrs].compact.join(" ") - "#<#{display}>" - end - end - alias_method :to_s, :inspect - - private - - def initialize_copy(source) super.freeze end - def marshal_dump; __to_h__ end - - def __to_h__; Hash[members.map {|m| [m, send(m)] }] end - - # Yields +true+ if +obj+ has been seen already, +false+ if it hasn't. - # Marks +obj+ as seen inside the block, so circuler references don't - # recursively trigger a SystemStackError (stack level too deep). - # - # Making circular references inside a Data object _should_ be very - # uncommon, but we'll support them for the sake of completeness. - def __inspect_guard__(obj) - preexisting = Thread.current[:__net_imap_data__inspect__] - Thread.current[:__net_imap_data__inspect__] ||= {}.compare_by_identity - inspect_guard = Thread.current[:__net_imap_data__inspect__] - if inspect_guard.include?(obj) - yield true - else - begin - inspect_guard[obj] = true - yield false - ensure - inspect_guard.delete(obj) - end - end - ensure - unless preexisting.equal?(inspect_guard) - Thread.current[:__net_imap_data__inspect__] = preexisting - end - end - - end - - end -end -# :nocov: