|
27 | 27 |
|
28 | 28 | module Net |
29 | 29 | 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 |
32 | 37 | def encode_with(coder) coder.map = to_h.transform_keys(&:to_s) end |
33 | 38 | def init_with(coder) initialize(**coder.map.transform_keys(&:to_sym)) end |
34 | 39 | end |
35 | 40 |
|
36 | 41 | Data = DataLite |
37 | 42 | end |
38 | 43 | 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