net-imap 0.4.6 → 0.4.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/pages.yml +2 -2
- data/lib/net/imap/response_data.rb +65 -6
- data/lib/net/imap/response_parser.rb +53 -39
- data/lib/net/imap/search_result.rb +150 -0
- data/lib/net/imap/sequence_set.rb +1318 -36
- data/lib/net/imap.rb +165 -35
- data/net-imap.gemspec +1 -0
- metadata +4 -2
@@ -4,64 +4,1346 @@ module Net
|
|
4
4
|
class IMAP
|
5
5
|
|
6
6
|
##
|
7
|
-
# An IMAP
|
8
|
-
#
|
9
|
-
#
|
10
|
-
# ("
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
7
|
+
# An \IMAP sequence set is a set of message sequence numbers or unique
|
8
|
+
# identifier numbers ("UIDs"). It contains numbers and ranges of numbers.
|
9
|
+
# The numbers are all non-zero unsigned 32-bit integers and one special
|
10
|
+
# value (<tt>"*"</tt>) that represents the largest value in the mailbox.
|
11
|
+
#
|
12
|
+
# Certain types of \IMAP responses will contain a SequenceSet, for example
|
13
|
+
# the data for a <tt>"MODIFIED"</tt> ResponseCode. Some \IMAP commands may
|
14
|
+
# receive a SequenceSet as an argument, for example IMAP#search, IMAP#fetch,
|
15
|
+
# and IMAP#store.
|
16
|
+
#
|
17
|
+
# == EXPERIMENTAL API
|
18
|
+
#
|
19
|
+
# SequenceSet is currently experimental. Only two methods, ::[] and
|
20
|
+
# #valid_string, are considered stable. Although the API isn't expected to
|
21
|
+
# change much, any other methods may be removed or changed without
|
22
|
+
# deprecation.
|
23
|
+
#
|
24
|
+
# == Creating sequence sets
|
25
|
+
#
|
26
|
+
# SequenceSet.new with no arguments creates an empty sequence set. Note
|
27
|
+
# that an empty sequence set is invalid in the \IMAP grammar.
|
28
|
+
#
|
29
|
+
# set = Net::IMAP::SequenceSet.new
|
30
|
+
# set.empty? #=> true
|
31
|
+
# set.valid? #=> false
|
32
|
+
# set.valid_string #!> raises DataFormatError
|
33
|
+
# set << 1..10
|
34
|
+
# set.empty? #=> false
|
35
|
+
# set.valid? #=> true
|
36
|
+
# set.valid_string #=> "1:10"
|
37
|
+
#
|
38
|
+
# SequenceSet.new may receive a single optional argument: a non-zero 32 bit
|
39
|
+
# unsigned integer, a range, a <tt>sequence-set</tt> formatted string,
|
40
|
+
# another sequence set, or an enumerable containing any of these.
|
41
|
+
#
|
42
|
+
# set = Net::IMAP::SequenceSet.new(1)
|
43
|
+
# set.valid_string #=> "1"
|
44
|
+
# set = Net::IMAP::SequenceSet.new(1..100)
|
45
|
+
# set.valid_string #=> "1:100"
|
46
|
+
# set = Net::IMAP::SequenceSet.new(1...100)
|
47
|
+
# set.valid_string #=> "1:99"
|
48
|
+
# set = Net::IMAP::SequenceSet.new([1, 2, 5..])
|
49
|
+
# set.valid_string #=> "1:2,5:*"
|
50
|
+
# set = Net::IMAP::SequenceSet.new("1,2,3:7,5,6:10,2048,1024")
|
51
|
+
# set.valid_string #=> "1,2,3:7,5,6:10,2048,1024"
|
52
|
+
# set = Net::IMAP::SequenceSet.new(1, 2, 3..7, 5, 6..10, 2048, 1024)
|
53
|
+
# set.valid_string #=> "1:10,55,1024:2048"
|
54
|
+
#
|
55
|
+
# Use ::[] with one or more arguments to create a frozen SequenceSet. An
|
56
|
+
# invalid (empty) set cannot be created with ::[].
|
57
|
+
#
|
58
|
+
# set = Net::IMAP::SequenceSet["1,2,3:7,5,6:10,2048,1024"]
|
59
|
+
# set.valid_string #=> "1,2,3:7,5,6:10,2048,1024"
|
60
|
+
# set = Net::IMAP::SequenceSet[1, 2, [3..7, 5], 6..10, 2048, 1024]
|
61
|
+
# set.valid_string #=> "1:10,55,1024:2048"
|
62
|
+
#
|
63
|
+
# == Using <tt>*</tt>
|
64
|
+
#
|
65
|
+
# \IMAP sequence sets may contain a special value <tt>"*"</tt>, which
|
66
|
+
# represents the largest number in use. From +seq-number+ in
|
67
|
+
# {RFC9051 §9}[https://www.rfc-editor.org/rfc/rfc9051.html#section-9-5]:
|
68
|
+
# >>>
|
69
|
+
# In the case of message sequence numbers, it is the number of messages
|
70
|
+
# in a non-empty mailbox. In the case of unique identifiers, it is the
|
71
|
+
# unique identifier of the last message in the mailbox or, if the
|
72
|
+
# mailbox is empty, the mailbox's current UIDNEXT value.
|
73
|
+
#
|
74
|
+
# When creating a SequenceSet, <tt>*</tt> may be input as <tt>-1</tt>,
|
75
|
+
# <tt>"*"</tt>, <tt>:*</tt>, an endless range, or a range ending in
|
76
|
+
# <tt>-1</tt>. When converting to #elements, #ranges, or #numbers, it will
|
77
|
+
# output as either <tt>:*</tt> or an endless range. For example:
|
78
|
+
#
|
79
|
+
# Net::IMAP::SequenceSet["1,3,*"].to_a #=> [1, 3, :*]
|
80
|
+
# Net::IMAP::SequenceSet["1,234:*"].to_a #=> [1, 234..]
|
81
|
+
# Net::IMAP::SequenceSet[1234..-1].to_a #=> [1234..]
|
82
|
+
# Net::IMAP::SequenceSet[1234..].to_a #=> [1234..]
|
83
|
+
#
|
84
|
+
# Net::IMAP::SequenceSet[1234..].to_s #=> "1234:*"
|
85
|
+
# Net::IMAP::SequenceSet[1234..-1].to_s #=> "1234:*"
|
86
|
+
#
|
87
|
+
# Use #limit to convert <tt>"*"</tt> to a maximum value. When a range
|
88
|
+
# includes <tt>"*"</tt>, the maximum value will always be matched:
|
89
|
+
#
|
90
|
+
# Net::IMAP::SequenceSet["9999:*"].limit(max: 25)
|
91
|
+
# #=> Net::IMAP::SequenceSet["25"]
|
92
|
+
#
|
93
|
+
# === Surprising <tt>*</tt> behavior
|
94
|
+
#
|
95
|
+
# When a set includes <tt>*</tt>, some methods may have surprising behavior.
|
96
|
+
#
|
97
|
+
# For example, #complement treats <tt>*</tt> as its own number. This way,
|
98
|
+
# the #intersection of a set and its #complement will always be empty.
|
99
|
+
# This is not how an \IMAP server interprets the set: it will convert
|
100
|
+
# <tt>*</tt> to either the number of messages in the mailbox or +UIDNEXT+,
|
101
|
+
# as appropriate. And there _will_ be overlap between a set and its
|
102
|
+
# complement after #limit is applied to each:
|
103
|
+
#
|
104
|
+
# ~Net::IMAP::SequenceSet["*"] == Net::IMAP::SequenceSet[1..(2**32-1)]
|
105
|
+
# ~Net::IMAP::SequenceSet[1..5] == Net::IMAP::SequenceSet["6:*"]
|
106
|
+
#
|
107
|
+
# set = Net::IMAP::SequenceSet[1..5]
|
108
|
+
# (set & ~set).empty? => true
|
109
|
+
#
|
110
|
+
# (set.limit(max: 4) & (~set).limit(max: 4)).to_a => [4]
|
111
|
+
#
|
112
|
+
# When counting the number of numbers in a set, <tt>*</tt> will be counted
|
113
|
+
# _except_ when UINT32_MAX is also in the set:
|
114
|
+
# UINT32_MAX = 2**32 - 1
|
115
|
+
# Net::IMAP::SequenceSet["*"].count => 1
|
116
|
+
# Net::IMAP::SequenceSet[1..UINT32_MAX - 1, :*].count => UINT32_MAX
|
117
|
+
#
|
118
|
+
# Net::IMAP::SequenceSet["1:*"].count => UINT32_MAX
|
119
|
+
# Net::IMAP::SequenceSet[UINT32_MAX, :*].count => 1
|
120
|
+
# Net::IMAP::SequenceSet[UINT32_MAX..].count => 1
|
121
|
+
#
|
122
|
+
# == What's here?
|
123
|
+
#
|
124
|
+
# SequenceSet provides methods for:
|
125
|
+
# * {Creating a SequenceSet}[rdoc-ref:SequenceSet@Methods+for+Creating+a+SequenceSet]
|
126
|
+
# * {Comparing}[rdoc-ref:SequenceSet@Methods+for+Comparing]
|
127
|
+
# * {Querying}[rdoc-ref:SequenceSet@Methods+for+Querying]
|
128
|
+
# * {Iterating}[rdoc-ref:SequenceSet@Methods+for+Iterating]
|
129
|
+
# * {Set Operations}[rdoc-ref:SequenceSet@Methods+for+Set+Operations]
|
130
|
+
# * {Assigning}[rdoc-ref:SequenceSet@Methods+for+Assigning]
|
131
|
+
# * {Deleting}[rdoc-ref:SequenceSet@Methods+for+Deleting]
|
132
|
+
# * {IMAP String Formatting}[rdoc-ref:SequenceSet@Methods+for+IMAP+String+Formatting]
|
133
|
+
#
|
134
|
+
# === Methods for Creating a \SequenceSet
|
135
|
+
# * ::[]: Creates a validated frozen sequence set from one or more inputs.
|
136
|
+
# * ::new: Creates a new mutable sequence set, which may be empty (invalid).
|
137
|
+
# * ::try_convert: Calls +to_sequence_set+ on an object and verifies that
|
138
|
+
# the result is a SequenceSet.
|
139
|
+
# * ::empty: Returns a frozen empty (invalid) SequenceSet.
|
140
|
+
# * ::full: Returns a frozen SequenceSet containing every possible number.
|
141
|
+
#
|
142
|
+
# === Methods for Comparing
|
143
|
+
#
|
144
|
+
# <i>Comparison to another \SequenceSet:</i>
|
145
|
+
# - #==: Returns whether a given set contains the same numbers as +self+.
|
146
|
+
# - #eql?: Returns whether a given set uses the same #string as +self+.
|
147
|
+
#
|
148
|
+
# <i>Comparison to objects which are convertible to \SequenceSet:</i>
|
149
|
+
# - #===:
|
150
|
+
# Returns whether a given object is fully contained within +self+, or
|
151
|
+
# +nil+ if the object cannot be converted to a compatible type.
|
152
|
+
# - #cover? (aliased as #===):
|
153
|
+
# Returns whether a given object is fully contained within +self+.
|
154
|
+
# - #intersect?:
|
155
|
+
# Returns whether +self+ and a given object have any common elements.
|
156
|
+
# - #disjoint?:
|
157
|
+
# Returns whether +self+ and a given object have no common elements.
|
158
|
+
#
|
159
|
+
# === Methods for Querying
|
160
|
+
# These methods do not modify +self+.
|
161
|
+
#
|
162
|
+
# <i>Set membership:</i>
|
163
|
+
# - #include? (aliased as #member?):
|
164
|
+
# Returns whether a given object (nz-number, range, or <tt>*</tt>) is
|
165
|
+
# contained by the set.
|
166
|
+
# - #include_star?: Returns whether the set contains <tt>*</tt>.
|
167
|
+
#
|
168
|
+
# <i>Minimum and maximum value elements:</i>
|
169
|
+
# - #min: Returns the minimum number in the set.
|
170
|
+
# - #max: Returns the maximum number in the set.
|
171
|
+
# - #minmax: Returns the minimum and maximum numbers in the set.
|
172
|
+
#
|
173
|
+
# <i>Accessing value by offset:</i>
|
174
|
+
# - #[] (aliased as #slice): Returns the number or consecutive subset at a
|
175
|
+
# given offset or range of offsets.
|
176
|
+
# - #at: Returns the number at a given offset.
|
177
|
+
# - #find_index: Returns the given number's offset in the set
|
178
|
+
#
|
179
|
+
# <i>Set cardinality:</i>
|
180
|
+
# - #count (aliased as #size): Returns the count of numbers in the set.
|
181
|
+
# - #empty?: Returns whether the set has no members. \IMAP syntax does not
|
182
|
+
# allow empty sequence sets.
|
183
|
+
# - #valid?: Returns whether the set has any members.
|
184
|
+
# - #full?: Returns whether the set contains every possible value, including
|
185
|
+
# <tt>*</tt>.
|
186
|
+
#
|
187
|
+
# === Methods for Iterating
|
188
|
+
#
|
189
|
+
# - #each_element: Yields each number and range in the set and returns
|
190
|
+
# +self+.
|
191
|
+
# - #elements (aliased as #to_a):
|
192
|
+
# Returns an Array of every number and range in the set.
|
193
|
+
# - #each_range:
|
194
|
+
# Yields each element in the set as a Range and returns +self+.
|
195
|
+
# - #ranges: Returns an Array of every element in the set, converting
|
196
|
+
# numbers into ranges of a single value.
|
197
|
+
# - #each_number: Yields each number in the set and returns +self+.
|
198
|
+
# - #numbers: Returns an Array with every number in the set, expanding
|
199
|
+
# ranges into all of their contained numbers.
|
200
|
+
# - #to_set: Returns a Set containing all of the #numbers in the set.
|
201
|
+
#
|
202
|
+
# === Methods for \Set Operations
|
203
|
+
# These methods do not modify +self+.
|
204
|
+
#
|
205
|
+
# - #| (aliased as #union and #+): Returns a new set combining all members
|
206
|
+
# from +self+ with all members from the other object.
|
207
|
+
# - #& (aliased as #intersection): Returns a new set containing all members
|
208
|
+
# common to +self+ and the other object.
|
209
|
+
# - #- (aliased as #difference): Returns a copy of +self+ with all members
|
210
|
+
# in the other object removed.
|
211
|
+
# - #^ (aliased as #xor): Returns a new set containing all members from
|
212
|
+
# +self+ and the other object except those common to both.
|
213
|
+
# - #~ (aliased as #complement): Returns a new set containing all members
|
214
|
+
# that are not in +self+
|
215
|
+
# - #limit: Returns a copy of +self+ which has replaced <tt>*</tt> with a
|
216
|
+
# given maximum value and removed all members over that maximum.
|
217
|
+
#
|
218
|
+
# === Methods for Assigning
|
219
|
+
# These methods add or replace elements in +self+.
|
220
|
+
#
|
221
|
+
# - #add (aliased as #<<): Adds a given object to the set; returns +self+.
|
222
|
+
# - #add?: If the given object is not an element in the set, adds it and
|
223
|
+
# returns +self+; otherwise, returns +nil+.
|
224
|
+
# - #merge: Merges multiple elements into the set; returns +self+.
|
225
|
+
# - #string=: Assigns a new #string value and replaces #elements to match.
|
226
|
+
# - #replace: Replaces the contents of the set with the contents
|
227
|
+
# of a given object.
|
228
|
+
# - #complement!: Replaces the contents of the set with its own #complement.
|
229
|
+
#
|
230
|
+
# === Methods for Deleting
|
231
|
+
# These methods remove elements from +self+.
|
232
|
+
#
|
233
|
+
# - #clear: Removes all elements in the set; returns +self+.
|
234
|
+
# - #delete: Removes a given object from the set; returns +self+.
|
235
|
+
# - #delete?: If the given object is an element in the set, removes it and
|
236
|
+
# returns it; otherwise, returns +nil+.
|
237
|
+
# - #delete_at: Removes the number at a given offset.
|
238
|
+
# - #slice!: Removes the number or consecutive numbers at a given offset or
|
239
|
+
# range of offsets.
|
240
|
+
# - #subtract: Removes each given object from the set; returns +self+.
|
241
|
+
# - #limit!: Replaces <tt>*</tt> with a given maximum value and removes all
|
242
|
+
# members over that maximum; returns +self+.
|
243
|
+
#
|
244
|
+
# === Methods for \IMAP String Formatting
|
245
|
+
#
|
246
|
+
# - #to_s: Returns the +sequence-set+ string, or an empty string when the
|
247
|
+
# set is empty.
|
248
|
+
# - #string: Returns the +sequence-set+ string, or nil when empty.
|
249
|
+
# - #valid_string: Returns the +sequence-set+ string, or raises
|
250
|
+
# DataFormatError when the set is empty.
|
251
|
+
# - #normalized_string: Returns a <tt>sequence-set</tt> string with its
|
252
|
+
# elements sorted and coalesced, or nil when the set is empty.
|
253
|
+
# - #normalize: Returns a new set with this set's normalized +sequence-set+
|
254
|
+
# representation.
|
255
|
+
# - #normalize!: Updates #string to its normalized +sequence-set+
|
256
|
+
# representation and returns +self+.
|
257
|
+
#
|
17
258
|
class SequenceSet
|
259
|
+
# The largest possible non-zero unsigned 32-bit integer
|
260
|
+
UINT32_MAX = 2**32 - 1
|
261
|
+
|
262
|
+
# represents "*" internally, to simplify sorting (etc)
|
263
|
+
STAR_INT = UINT32_MAX + 1
|
264
|
+
private_constant :STAR_INT
|
265
|
+
|
266
|
+
# valid inputs for "*"
|
267
|
+
STARS = [:*, ?*, -1].freeze
|
268
|
+
private_constant :STAR_INT, :STARS
|
269
|
+
|
270
|
+
COERCIBLE = ->{ _1.respond_to? :to_sequence_set }
|
271
|
+
ENUMABLE = ->{ _1.respond_to?(:each) && _1.respond_to?(:empty?) }
|
272
|
+
private_constant :COERCIBLE, :ENUMABLE
|
273
|
+
|
274
|
+
class << self
|
275
|
+
|
276
|
+
# :call-seq:
|
277
|
+
# SequenceSet[*values] -> valid frozen sequence set
|
278
|
+
#
|
279
|
+
# Returns a frozen SequenceSet, constructed from +values+.
|
280
|
+
#
|
281
|
+
# An empty SequenceSet is invalid and will raise a DataFormatError.
|
282
|
+
#
|
283
|
+
# Use ::new to create a mutable or empty SequenceSet.
|
284
|
+
def [](first, *rest)
|
285
|
+
if rest.empty?
|
286
|
+
if first.is_a?(SequenceSet) && set.frozen? && set.valid?
|
287
|
+
first
|
288
|
+
else
|
289
|
+
new(first).validate.freeze
|
290
|
+
end
|
291
|
+
else
|
292
|
+
new(first).merge(*rest).validate.freeze
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
# :call-seq:
|
297
|
+
# SequenceSet.try_convert(obj) -> sequence set or nil
|
298
|
+
#
|
299
|
+
# If +obj+ is a SequenceSet, returns +obj+. If +obj+ responds_to
|
300
|
+
# +to_sequence_set+, calls +obj.to_sequence_set+ and returns the result.
|
301
|
+
# Otherwise returns +nil+.
|
302
|
+
#
|
303
|
+
# If +obj.to_sequence_set+ doesn't return a SequenceSet, an exception is
|
304
|
+
# raised.
|
305
|
+
def try_convert(obj)
|
306
|
+
return obj if obj.is_a?(SequenceSet)
|
307
|
+
return nil unless respond_to?(:to_sequence_set)
|
308
|
+
obj = obj.to_sequence_set
|
309
|
+
return obj if obj.is_a?(SequenceSet)
|
310
|
+
raise DataFormatError, "invalid object returned from to_sequence_set"
|
311
|
+
end
|
312
|
+
|
313
|
+
# Returns a frozen empty set singleton. Note that valid \IMAP sequence
|
314
|
+
# sets cannot be empty, so this set is _invalid_.
|
315
|
+
def empty; EMPTY end
|
316
|
+
|
317
|
+
# Returns a frozen full set singleton: <tt>"1:*"</tt>
|
318
|
+
def full; FULL end
|
319
|
+
|
320
|
+
end
|
321
|
+
|
322
|
+
# Create a new SequenceSet object from +input+, which may be another
|
323
|
+
# SequenceSet, an IMAP formatted +sequence-set+ string, a number, a
|
324
|
+
# range, <tt>:*</tt>, or an enumerable of these.
|
325
|
+
#
|
326
|
+
# Use ::[] to create a frozen (non-empty) SequenceSet.
|
327
|
+
def initialize(input = nil) input ? replace(input) : clear end
|
328
|
+
|
329
|
+
# Removes all elements and returns self.
|
330
|
+
def clear; @tuples, @string = [], nil; self end
|
18
331
|
|
19
|
-
|
332
|
+
# Replace the contents of the set with the contents of +other+ and returns
|
333
|
+
# +self+.
|
334
|
+
#
|
335
|
+
# +other+ may be another SequenceSet, or it may be an IMAP +sequence-set+
|
336
|
+
# string, a number, a range, <tt>*</tt>, or an enumerable of these.
|
337
|
+
def replace(other)
|
338
|
+
case other
|
339
|
+
when SequenceSet then initialize_dup(other)
|
340
|
+
when String then self.string = other
|
341
|
+
else clear; merge other
|
342
|
+
end
|
343
|
+
self
|
344
|
+
end
|
345
|
+
|
346
|
+
# Returns the \IMAP +sequence-set+ string representation, or raises a
|
347
|
+
# DataFormatError when the set is empty.
|
348
|
+
#
|
349
|
+
# Use #string to return +nil+ or #to_s to return an empty string without
|
350
|
+
# error.
|
351
|
+
#
|
352
|
+
# Related: #string, #normalized_string, #to_s
|
353
|
+
def valid_string
|
354
|
+
raise DataFormatError, "empty sequence-set" if empty?
|
355
|
+
string
|
356
|
+
end
|
357
|
+
|
358
|
+
# Returns the \IMAP +sequence-set+ string representation, or +nil+ when
|
359
|
+
# the set is empty. Note that an empty set is invalid in the \IMAP
|
360
|
+
# syntax.
|
361
|
+
#
|
362
|
+
# Use #valid_string to raise an exception when the set is empty, or #to_s
|
363
|
+
# to return an empty string.
|
364
|
+
#
|
365
|
+
# If the set was created from a single string, it is not normalized. If
|
366
|
+
# the set is updated the string will be normalized.
|
367
|
+
#
|
368
|
+
# Related: #valid_string, #normalized_string, #to_s
|
369
|
+
def string; @string ||= normalized_string if valid? end
|
20
370
|
|
21
|
-
|
22
|
-
|
23
|
-
|
371
|
+
# Assigns a new string to #string and resets #elements to match. It
|
372
|
+
# cannot be set to an empty string—assign +nil+ or use #clear instead.
|
373
|
+
# The string is validated but not normalized.
|
374
|
+
#
|
375
|
+
# Use #add or #merge to add a string to an existing set.
|
376
|
+
#
|
377
|
+
# Related: #replace, #clear
|
378
|
+
def string=(str)
|
379
|
+
if str.nil?
|
380
|
+
clear
|
381
|
+
else
|
382
|
+
str = String.try_convert(str) or raise ArgumentError, "not a string"
|
383
|
+
tuples = str_to_tuples str
|
384
|
+
@tuples, @string = [], -str
|
385
|
+
tuples_add tuples
|
386
|
+
end
|
24
387
|
end
|
25
388
|
|
26
|
-
# Returns the IMAP string representation
|
27
|
-
#
|
28
|
-
|
389
|
+
# Returns the \IMAP +sequence-set+ string representation, or an empty
|
390
|
+
# string when the set is empty. Note that an empty set is invalid in the
|
391
|
+
# \IMAP syntax.
|
392
|
+
#
|
393
|
+
# Related: #valid_string, #normalized_string, #to_s
|
394
|
+
def to_s; string || "" end
|
29
395
|
|
30
|
-
#
|
31
|
-
|
396
|
+
# Freezes and returns the set. A frozen SequenceSet is Ractor-safe.
|
397
|
+
def freeze
|
398
|
+
return self if frozen?
|
399
|
+
string
|
400
|
+
@tuples.each(&:freeze).freeze
|
401
|
+
super
|
402
|
+
end
|
32
403
|
|
33
|
-
#
|
34
|
-
|
404
|
+
# :call-seq: self == other -> true or false
|
405
|
+
#
|
406
|
+
# Returns true when the other SequenceSet represents the same message
|
407
|
+
# identifiers. Encoding difference—such as order, overlaps, or
|
408
|
+
# duplicates—are ignored.
|
409
|
+
#
|
410
|
+
# Net::IMAP::SequenceSet["1:3"] == Net::IMAP::SequenceSet["1:3"]
|
411
|
+
# #=> true
|
412
|
+
# Net::IMAP::SequenceSet["1,2,3"] == Net::IMAP::SequenceSet["1:3"]
|
413
|
+
# #=> true
|
414
|
+
# Net::IMAP::SequenceSet["1,3"] == Net::IMAP::SequenceSet["3,1"]
|
415
|
+
# #=> true
|
416
|
+
# Net::IMAP::SequenceSet["9,1:*"] == Net::IMAP::SequenceSet["1:*"]
|
417
|
+
# #=> true
|
418
|
+
#
|
419
|
+
# Related: #eql?, #normalize
|
420
|
+
def ==(other)
|
421
|
+
self.class == other.class &&
|
422
|
+
(to_s == other.to_s || tuples == other.tuples)
|
423
|
+
end
|
35
424
|
|
36
|
-
#
|
425
|
+
# :call-seq: eql?(other) -> true or false
|
37
426
|
#
|
38
|
-
#
|
39
|
-
# Net::IMAP::SequenceSet["1,2,3"].eql? Net::IMAP::SequenceSet["1:3"] # => false
|
40
|
-
# Net::IMAP::SequenceSet["1,3"] .eql? Net::IMAP::SequenceSet["3,1"] # => false
|
41
|
-
# Net::IMAP::SequenceSet["9,1:*"].eql? Net::IMAP::SequenceSet["1:*"] # => false
|
427
|
+
# Hash equality requires the same encoded #string representation.
|
42
428
|
#
|
43
|
-
|
44
|
-
|
429
|
+
# Net::IMAP::SequenceSet["1:3"] .eql? Net::IMAP::SequenceSet["1:3"]
|
430
|
+
# #=> true
|
431
|
+
# Net::IMAP::SequenceSet["1,2,3"].eql? Net::IMAP::SequenceSet["1:3"]
|
432
|
+
# #=> false
|
433
|
+
# Net::IMAP::SequenceSet["1,3"] .eql? Net::IMAP::SequenceSet["3,1"]
|
434
|
+
# #=> false
|
435
|
+
# Net::IMAP::SequenceSet["9,1:*"].eql? Net::IMAP::SequenceSet["1:*"]
|
436
|
+
# #=> false
|
437
|
+
#
|
438
|
+
# Related: #==, #normalize
|
439
|
+
def eql?(other) self.class == other.class && string == other.string end
|
45
440
|
|
46
441
|
# See #eql?
|
47
|
-
def hash; [self.class
|
442
|
+
def hash; [self.class, string].hash end
|
443
|
+
|
444
|
+
# :call-seq: self === other -> true | false | nil
|
445
|
+
#
|
446
|
+
# Returns whether +other+ is contained within the set. Returns +nil+ if a
|
447
|
+
# StandardError is raised while converting +other+ to a comparable type.
|
448
|
+
#
|
449
|
+
# Related: #cover?, #include?, #include_star?
|
450
|
+
def ===(other)
|
451
|
+
cover?(other)
|
452
|
+
rescue
|
453
|
+
nil
|
454
|
+
end
|
455
|
+
|
456
|
+
# :call-seq: cover?(other) -> true | false | nil
|
457
|
+
#
|
458
|
+
# Returns whether +other+ is contained within the set. +other+ may be any
|
459
|
+
# object that would be accepted by ::new.
|
460
|
+
#
|
461
|
+
# Related: #===, #include?, #include_star?
|
462
|
+
def cover?(other) input_to_tuples(other).none? { !include_tuple?(_1) } end
|
463
|
+
|
464
|
+
# Returns +true+ when a given number or range is in +self+, and +false+
|
465
|
+
# otherwise. Returns +false+ unless +number+ is an Integer, Range, or
|
466
|
+
# <tt>*</tt>.
|
467
|
+
#
|
468
|
+
# set = Net::IMAP::SequenceSet["5:10,100,111:115"]
|
469
|
+
# set.include? 1 #=> false
|
470
|
+
# set.include? 5..10 #=> true
|
471
|
+
# set.include? 11..20 #=> false
|
472
|
+
# set.include? 100 #=> true
|
473
|
+
# set.include? 6 #=> true, covered by "5:10"
|
474
|
+
# set.include? 4..9 #=> true, covered by "5:10"
|
475
|
+
# set.include? "4:9" #=> true, strings are parsed
|
476
|
+
# set.include? 4..9 #=> false, intersection is not sufficient
|
477
|
+
# set.include? "*" #=> false, use #limit to re-interpret "*"
|
478
|
+
# set.include? -1 #=> false, -1 is interpreted as "*"
|
479
|
+
#
|
480
|
+
# set = Net::IMAP::SequenceSet["5:10,100,111:*"]
|
481
|
+
# set.include? :* #=> true
|
482
|
+
# set.include? "*" #=> true
|
483
|
+
# set.include? -1 #=> true
|
484
|
+
# set.include? 200.. #=> true
|
485
|
+
# set.include? 100.. #=> false
|
486
|
+
#
|
487
|
+
# Related: #include_star?, #cover?, #===
|
488
|
+
def include?(element) include_tuple? input_to_tuple element end
|
489
|
+
|
490
|
+
alias member? include?
|
491
|
+
|
492
|
+
# Returns +true+ when the set contains <tt>*</tt>.
|
493
|
+
def include_star?; @tuples.last&.last == STAR_INT end
|
494
|
+
|
495
|
+
# Returns +true+ if the set and a given object have any common elements,
|
496
|
+
# +false+ otherwise.
|
497
|
+
#
|
498
|
+
# Net::IMAP::SequenceSet["5:10"].intersect? "7,9,11" #=> true
|
499
|
+
# Net::IMAP::SequenceSet["5:10"].intersect? "11:33" #=> false
|
500
|
+
#
|
501
|
+
# Related: #intersection, #disjoint?
|
502
|
+
def intersect?(other)
|
503
|
+
valid? && input_to_tuples(other).any? { intersect_tuple? _1 }
|
504
|
+
end
|
505
|
+
|
506
|
+
# Returns +true+ if the set and a given object have no common elements,
|
507
|
+
# +false+ otherwise.
|
508
|
+
#
|
509
|
+
# Net::IMAP::SequenceSet["5:10"].disjoint? "7,9,11" #=> false
|
510
|
+
# Net::IMAP::SequenceSet["5:10"].disjoint? "11:33" #=> true
|
511
|
+
#
|
512
|
+
# Related: #intersection, #intersect?
|
513
|
+
def disjoint?(other)
|
514
|
+
empty? || input_to_tuples(other).none? { intersect_tuple? _1 }
|
515
|
+
end
|
516
|
+
|
517
|
+
# :call-seq: max(star: :*) => integer or star or nil
|
518
|
+
#
|
519
|
+
# Returns the maximum value in +self+, +star+ when the set includes
|
520
|
+
# <tt>*</tt>, or +nil+ when the set is empty.
|
521
|
+
def max(star: :*)
|
522
|
+
(val = @tuples.last&.last) && val == STAR_INT ? star : val
|
523
|
+
end
|
524
|
+
|
525
|
+
# :call-seq: min(star: :*) => integer or star or nil
|
526
|
+
#
|
527
|
+
# Returns the minimum value in +self+, +star+ when the only value in the
|
528
|
+
# set is <tt>*</tt>, or +nil+ when the set is empty.
|
529
|
+
def min(star: :*)
|
530
|
+
(val = @tuples.first&.first) && val == STAR_INT ? star : val
|
531
|
+
end
|
532
|
+
|
533
|
+
# :call-seq: minmax(star: :*) => nil or [integer, integer or star]
|
534
|
+
#
|
535
|
+
# Returns a 2-element array containing the minimum and maximum numbers in
|
536
|
+
# +self+, or +nil+ when the set is empty.
|
537
|
+
def minmax(star: :*); [min(star: star), max(star: star)] unless empty? end
|
538
|
+
|
539
|
+
# Returns false when the set is empty.
|
540
|
+
def valid?; !empty? end
|
541
|
+
|
542
|
+
# Returns true if the set contains no elements
|
543
|
+
def empty?; @tuples.empty? end
|
544
|
+
|
545
|
+
# Returns true if the set contains every possible element.
|
546
|
+
def full?; @tuples == [[1, STAR_INT]] end
|
547
|
+
|
548
|
+
# :call-seq:
|
549
|
+
# self + other -> sequence set
|
550
|
+
# self | other -> sequence set
|
551
|
+
# union(other) -> sequence set
|
552
|
+
#
|
553
|
+
# Returns a new sequence set that has every number in the +other+ object
|
554
|
+
# added.
|
555
|
+
#
|
556
|
+
# +other+ may be any object that would be accepted by ::new: a non-zero 32
|
557
|
+
# bit unsigned integer, range, <tt>sequence-set</tt> formatted string,
|
558
|
+
# another sequence set, or an enumerable containing any of these.
|
559
|
+
#
|
560
|
+
# Net::IMAP::SequenceSet["1:5"] | 2 | [4..6, 99]
|
561
|
+
# #=> Net::IMAP::SequenceSet["1:6,99"]
|
562
|
+
#
|
563
|
+
# Related: #add, #merge
|
564
|
+
def |(other) remain_frozen dup.merge other end
|
565
|
+
alias :+ :|
|
566
|
+
alias union :|
|
567
|
+
|
568
|
+
# :call-seq:
|
569
|
+
# self - other -> sequence set
|
570
|
+
# difference(other) -> sequence set
|
571
|
+
#
|
572
|
+
# Returns a new sequence set built by duplicating this set and removing
|
573
|
+
# every number that appears in +other+.
|
574
|
+
#
|
575
|
+
# +other+ may be any object that would be accepted by ::new: a non-zero 32
|
576
|
+
# bit unsigned integer, range, <tt>sequence-set</tt> formatted string,
|
577
|
+
# another sequence set, or an enumerable containing any of these.
|
578
|
+
#
|
579
|
+
# Net::IMAP::SequenceSet[1..5] - 2 - 4 - 6
|
580
|
+
# #=> Net::IMAP::SequenceSet["1,3,5"]
|
581
|
+
#
|
582
|
+
# Related: #subtract
|
583
|
+
def -(other) remain_frozen dup.subtract other end
|
584
|
+
alias difference :-
|
585
|
+
|
586
|
+
# :call-seq:
|
587
|
+
# self & other -> sequence set
|
588
|
+
# intersection(other) -> sequence set
|
589
|
+
#
|
590
|
+
# Returns a new sequence set containing only the numbers common to this
|
591
|
+
# set and +other+.
|
592
|
+
#
|
593
|
+
# +other+ may be any object that would be accepted by ::new: a non-zero 32
|
594
|
+
# bit unsigned integer, range, <tt>sequence-set</tt> formatted string,
|
595
|
+
# another sequence set, or an enumerable containing any of these.
|
596
|
+
#
|
597
|
+
# Net::IMAP::SequenceSet[1..5] & [2, 4, 6]
|
598
|
+
# #=> Net::IMAP::SequenceSet["2,4"]
|
599
|
+
#
|
600
|
+
# <tt>(seqset & other)</tt> is equivalent to <tt>(seqset - ~other)</tt>.
|
601
|
+
def &(other)
|
602
|
+
remain_frozen dup.subtract SequenceSet.new(other).complement!
|
603
|
+
end
|
604
|
+
alias intersection :&
|
605
|
+
|
606
|
+
# :call-seq:
|
607
|
+
# self ^ other -> sequence set
|
608
|
+
# xor(other) -> sequence set
|
609
|
+
#
|
610
|
+
# Returns a new sequence set containing numbers that are exclusive between
|
611
|
+
# this set and +other+.
|
612
|
+
#
|
613
|
+
# +other+ may be any object that would be accepted by ::new: a non-zero 32
|
614
|
+
# bit unsigned integer, range, <tt>sequence-set</tt> formatted string,
|
615
|
+
# another sequence set, or an enumerable containing any of these.
|
616
|
+
#
|
617
|
+
# Net::IMAP::SequenceSet[1..5] ^ [2, 4, 6]
|
618
|
+
# #=> Net::IMAP::SequenceSet["1,3,5:6"]
|
619
|
+
#
|
620
|
+
# <tt>(seqset ^ other)</tt> is equivalent to <tt>((seqset | other) -
|
621
|
+
# (seqset & other))</tt>.
|
622
|
+
def ^(other) remain_frozen (self | other).subtract(self & other) end
|
623
|
+
alias xor :^
|
624
|
+
|
625
|
+
# :call-seq:
|
626
|
+
# ~ self -> sequence set
|
627
|
+
# complement -> sequence set
|
628
|
+
#
|
629
|
+
# Returns the complement of self, a SequenceSet which contains all numbers
|
630
|
+
# _except_ for those in this set.
|
631
|
+
#
|
632
|
+
# ~Net::IMAP::SequenceSet.full #=> Net::IMAP::SequenceSet.empty
|
633
|
+
# ~Net::IMAP::SequenceSet.empty #=> Net::IMAP::SequenceSet.full
|
634
|
+
# ~Net::IMAP::SequenceSet["1:5,100:222"]
|
635
|
+
# #=> Net::IMAP::SequenceSet["6:99,223:*"]
|
636
|
+
# ~Net::IMAP::SequenceSet["6:99,223:*"]
|
637
|
+
# #=> Net::IMAP::SequenceSet["1:5,100:222"]
|
638
|
+
#
|
639
|
+
# Related: #complement!
|
640
|
+
def ~; remain_frozen dup.complement! end
|
641
|
+
alias complement :~
|
642
|
+
|
643
|
+
# :call-seq:
|
644
|
+
# add(object) -> self
|
645
|
+
# self << other -> self
|
646
|
+
#
|
647
|
+
# Adds a range or number to the set and returns +self+.
|
648
|
+
#
|
649
|
+
# #string will be regenerated. Use #merge to add many elements at once.
|
650
|
+
#
|
651
|
+
# Related: #add?, #merge, #union
|
652
|
+
def add(object)
|
653
|
+
tuple_add input_to_tuple object
|
654
|
+
normalize!
|
655
|
+
end
|
656
|
+
alias << add
|
657
|
+
|
658
|
+
# :call-seq: add?(object) -> self or nil
|
659
|
+
#
|
660
|
+
# Adds a range or number to the set and returns +self+. Returns +nil+
|
661
|
+
# when the object is already included in the set.
|
662
|
+
#
|
663
|
+
# #string will be regenerated. Use #merge to add many elements at once.
|
664
|
+
#
|
665
|
+
# Related: #add, #merge, #union, #include?
|
666
|
+
def add?(object)
|
667
|
+
add object unless include? object
|
668
|
+
end
|
669
|
+
|
670
|
+
# :call-seq: delete(object) -> self
|
671
|
+
#
|
672
|
+
# Deletes the given range or number from the set and returns +self+.
|
673
|
+
#
|
674
|
+
# #string will be regenerated after deletion. Use #subtract to remove
|
675
|
+
# many elements at once.
|
676
|
+
#
|
677
|
+
# Related: #delete?, #delete_at, #subtract, #difference
|
678
|
+
def delete(object)
|
679
|
+
tuple_subtract input_to_tuple object
|
680
|
+
normalize!
|
681
|
+
end
|
682
|
+
|
683
|
+
# :call-seq:
|
684
|
+
# delete?(number) -> integer or nil
|
685
|
+
# delete?(star) -> :* or nil
|
686
|
+
# delete?(range) -> sequence set or nil
|
687
|
+
#
|
688
|
+
# Removes a specified value from the set, and returns the removed value.
|
689
|
+
# Returns +nil+ if nothing was removed.
|
690
|
+
#
|
691
|
+
# Returns an integer when the specified +number+ argument was removed:
|
692
|
+
# set = Net::IMAP::SequenceSet.new [5..10, 20]
|
693
|
+
# set.delete?(7) #=> 7
|
694
|
+
# set #=> #<Net::IMAP::SequenceSet "5:6,8:10,20">
|
695
|
+
# set.delete?("20") #=> 20
|
696
|
+
# set #=> #<Net::IMAP::SequenceSet "5:6,8:10">
|
697
|
+
# set.delete?(30) #=> nil
|
698
|
+
#
|
699
|
+
# Returns <tt>:*</tt> when <tt>*</tt> or <tt>-1</tt> is specified and
|
700
|
+
# removed:
|
701
|
+
# set = Net::IMAP::SequenceSet.new "5:9,20,35,*"
|
702
|
+
# set.delete?(-1) #=> :*
|
703
|
+
# set #=> #<Net::IMAP::SequenceSet "5:9,20,35">
|
704
|
+
#
|
705
|
+
# And returns a new SequenceSet when a range is specified:
|
706
|
+
#
|
707
|
+
# set = Net::IMAP::SequenceSet.new [5..10, 20]
|
708
|
+
# set.delete?(9..) #=> #<Net::IMAP::SequenceSet "9:10,20">
|
709
|
+
# set #=> #<Net::IMAP::SequenceSet "5:8">
|
710
|
+
# set.delete?(21..) #=> nil
|
711
|
+
#
|
712
|
+
# #string will be regenerated after deletion.
|
713
|
+
#
|
714
|
+
# Related: #delete, #delete_at, #subtract, #difference, #disjoint?
|
715
|
+
def delete?(object)
|
716
|
+
tuple = input_to_tuple object
|
717
|
+
if tuple.first == tuple.last
|
718
|
+
return unless include_tuple? tuple
|
719
|
+
tuple_subtract tuple
|
720
|
+
normalize!
|
721
|
+
from_tuple_int tuple.first
|
722
|
+
else
|
723
|
+
copy = dup
|
724
|
+
tuple_subtract tuple
|
725
|
+
normalize!
|
726
|
+
copy if copy.subtract(self).valid?
|
727
|
+
end
|
728
|
+
end
|
729
|
+
|
730
|
+
# :call-seq: delete_at(index) -> number or :* or nil
|
731
|
+
#
|
732
|
+
# Deletes a number the set, indicated by the given +index+. Returns the
|
733
|
+
# number that was removed, or +nil+ if nothing was removed.
|
734
|
+
#
|
735
|
+
# #string will be regenerated after deletion.
|
736
|
+
#
|
737
|
+
# Related: #delete, #delete?, #slice!, #subtract, #difference
|
738
|
+
def delete_at(index)
|
739
|
+
slice! Integer(index.to_int)
|
740
|
+
end
|
741
|
+
|
742
|
+
# :call-seq:
|
743
|
+
# slice!(index) -> integer or :* or nil
|
744
|
+
# slice!(start, length) -> sequence set or nil
|
745
|
+
# slice!(range) -> sequence set or nil
|
746
|
+
#
|
747
|
+
# Deletes a number or consecutive numbers from the set, indicated by the
|
748
|
+
# given +index+, +start+ and +length+, or +range+ of offsets. Returns the
|
749
|
+
# number or sequence set that was removed, or +nil+ if nothing was
|
750
|
+
# removed. Arguments are interpreted the same as for #slice or #[].
|
751
|
+
#
|
752
|
+
# #string will be regenerated after deletion.
|
753
|
+
#
|
754
|
+
# Related: #slice, #delete_at, #delete, #delete?, #subtract, #difference
|
755
|
+
def slice!(index, length = nil)
|
756
|
+
deleted = slice(index, length) and subtract deleted
|
757
|
+
deleted
|
758
|
+
end
|
759
|
+
|
760
|
+
# Merges all of the elements that appear in any of the +inputs+ into the
|
761
|
+
# set, and returns +self+.
|
762
|
+
#
|
763
|
+
# The +inputs+ may be any objects that would be accepted by ::new:
|
764
|
+
# non-zero 32 bit unsigned integers, ranges, <tt>sequence-set</tt>
|
765
|
+
# formatted strings, other sequence sets, or enumerables containing any of
|
766
|
+
# these.
|
767
|
+
#
|
768
|
+
# #string will be regenerated after all inputs have been merged.
|
769
|
+
#
|
770
|
+
# Related: #add, #add?, #union
|
771
|
+
def merge(*inputs)
|
772
|
+
tuples_add input_to_tuples inputs
|
773
|
+
normalize!
|
774
|
+
end
|
775
|
+
|
776
|
+
# Removes all of the elements that appear in any of the given +objects+
|
777
|
+
# from the set, and returns +self+.
|
778
|
+
#
|
779
|
+
# The +objects+ may be any objects that would be accepted by ::new:
|
780
|
+
# non-zero 32 bit unsigned integers, ranges, <tt>sequence-set</tt>
|
781
|
+
# formatted strings, other sequence sets, or enumerables containing any of
|
782
|
+
# these.
|
783
|
+
#
|
784
|
+
# Related: #difference
|
785
|
+
def subtract(*objects)
|
786
|
+
tuples_subtract input_to_tuples objects
|
787
|
+
normalize!
|
788
|
+
end
|
789
|
+
|
790
|
+
# Returns an array of ranges and integers.
|
791
|
+
#
|
792
|
+
# The returned elements are sorted and coalesced, even when the input
|
793
|
+
# #string is not. <tt>*</tt> will sort last. See #normalize.
|
794
|
+
#
|
795
|
+
# By itself, <tt>*</tt> translates to <tt>:*</tt>. A range containing
|
796
|
+
# <tt>*</tt> translates to an endless range. Use #limit to translate both
|
797
|
+
# cases to a maximum value.
|
798
|
+
#
|
799
|
+
# If the original input was unordered or contains overlapping ranges, the
|
800
|
+
# returned ranges will be ordered and coalesced.
|
801
|
+
#
|
802
|
+
# Net::IMAP::SequenceSet["2,5:9,6,*,12:11"].elements
|
803
|
+
# #=> [2, 5..9, 11..12, :*]
|
804
|
+
#
|
805
|
+
# Related: #each_element, #ranges, #numbers
|
806
|
+
def elements; each_element.to_a end
|
807
|
+
alias to_a elements
|
808
|
+
|
809
|
+
# Returns an array of ranges
|
810
|
+
#
|
811
|
+
# The returned elements are sorted and coalesced, even when the input
|
812
|
+
# #string is not. <tt>*</tt> will sort last. See #normalize.
|
813
|
+
#
|
814
|
+
# <tt>*</tt> translates to an endless range. By itself, <tt>*</tt>
|
815
|
+
# translates to <tt>:*..</tt>. Use #limit to set <tt>*</tt> to a maximum
|
816
|
+
# value.
|
817
|
+
#
|
818
|
+
# The returned ranges will be ordered and coalesced, even when the input
|
819
|
+
# #string is not. <tt>*</tt> will sort last. See #normalize.
|
820
|
+
#
|
821
|
+
# Net::IMAP::SequenceSet["2,5:9,6,*,12:11"].ranges
|
822
|
+
# #=> [2..2, 5..9, 11..12, :*..]
|
823
|
+
# Net::IMAP::SequenceSet["123,999:*,456:789"].ranges
|
824
|
+
# #=> [123..123, 456..789, 999..]
|
825
|
+
#
|
826
|
+
# Related: #each_range, #elements, #numbers, #to_set
|
827
|
+
def ranges; each_range.to_a end
|
828
|
+
|
829
|
+
# Returns a sorted array of all of the number values in the sequence set.
|
830
|
+
#
|
831
|
+
# The returned numbers are sorted and de-duplicated, even when the input
|
832
|
+
# #string is not. See #normalize.
|
833
|
+
#
|
834
|
+
# Net::IMAP::SequenceSet["2,5:9,6,12:11"].numbers
|
835
|
+
# #=> [2, 5, 6, 7, 8, 9, 11, 12]
|
836
|
+
#
|
837
|
+
# If the set contains a <tt>*</tt>, RangeError is raised. See #limit.
|
838
|
+
#
|
839
|
+
# Net::IMAP::SequenceSet["10000:*"].numbers
|
840
|
+
# #!> RangeError
|
841
|
+
#
|
842
|
+
# *WARNING:* Even excluding sets with <tt>*</tt>, an enormous result can
|
843
|
+
# easily be created. An array with over 4 billion integers could be
|
844
|
+
# returned, requiring up to 32GiB of memory on a 64-bit architecture.
|
845
|
+
#
|
846
|
+
# Net::IMAP::SequenceSet[10000..2**32-1].numbers
|
847
|
+
# # ...probably freezes the process for a while...
|
848
|
+
# #!> NoMemoryError (probably)
|
849
|
+
#
|
850
|
+
# For safety, consider using #limit or #intersection to set an upper
|
851
|
+
# bound. Alternatively, use #each_element, #each_range, or even
|
852
|
+
# #each_number to avoid allocation of a result array.
|
853
|
+
#
|
854
|
+
# Related: #elements, #ranges, #to_set
|
855
|
+
def numbers; each_number.to_a end
|
856
|
+
|
857
|
+
# Yields each number or range in #elements to the block and returns self.
|
858
|
+
# Returns an enumerator when called without a block.
|
859
|
+
#
|
860
|
+
# Related: #elements
|
861
|
+
def each_element # :yields: integer or range or :*
|
862
|
+
return to_enum(__method__) unless block_given?
|
863
|
+
@tuples.each do |min, max|
|
864
|
+
if min == STAR_INT then yield :*
|
865
|
+
elsif max == STAR_INT then yield min..
|
866
|
+
elsif min == max then yield min
|
867
|
+
else yield min..max
|
868
|
+
end
|
869
|
+
end
|
870
|
+
self
|
871
|
+
end
|
872
|
+
|
873
|
+
# Yields each range in #ranges to the block and returns self.
|
874
|
+
# Returns an enumerator when called without a block.
|
875
|
+
#
|
876
|
+
# Related: #ranges
|
877
|
+
def each_range # :yields: range
|
878
|
+
return to_enum(__method__) unless block_given?
|
879
|
+
@tuples.each do |min, max|
|
880
|
+
if min == STAR_INT then yield :*..
|
881
|
+
elsif max == STAR_INT then yield min..
|
882
|
+
else yield min..max
|
883
|
+
end
|
884
|
+
end
|
885
|
+
self
|
886
|
+
end
|
887
|
+
|
888
|
+
# Yields each number in #numbers to the block and returns self.
|
889
|
+
# If the set contains a <tt>*</tt>, RangeError will be raised.
|
890
|
+
#
|
891
|
+
# Returns an enumerator when called without a block (even if the set
|
892
|
+
# contains <tt>*</tt>).
|
893
|
+
#
|
894
|
+
# Related: #numbers
|
895
|
+
def each_number(&block) # :yields: integer
|
896
|
+
return to_enum(__method__) unless block_given?
|
897
|
+
raise RangeError, '%s contains "*"' % [self.class] if include_star?
|
898
|
+
each_element do |elem|
|
899
|
+
case elem
|
900
|
+
when Range then elem.each(&block)
|
901
|
+
when Integer then block.(elem)
|
902
|
+
end
|
903
|
+
end
|
904
|
+
self
|
905
|
+
end
|
906
|
+
|
907
|
+
# Returns a Set with all of the #numbers in the sequence set.
|
908
|
+
#
|
909
|
+
# If the set contains a <tt>*</tt>, RangeError will be raised.
|
910
|
+
#
|
911
|
+
# See #numbers for the warning about very large sets.
|
912
|
+
#
|
913
|
+
# Related: #elements, #ranges, #numbers
|
914
|
+
def to_set; Set.new(numbers) end
|
915
|
+
|
916
|
+
# Returns the count of #numbers in the set.
|
917
|
+
#
|
918
|
+
# If <tt>*</tt> and <tt>2**32 - 1</tt> (the maximum 32-bit unsigned
|
919
|
+
# integer value) are both in the set, they will only be counted once.
|
920
|
+
def count
|
921
|
+
@tuples.sum(@tuples.count) { _2 - _1 } +
|
922
|
+
(include_star? && include?(UINT32_MAX) ? -1 : 0)
|
923
|
+
end
|
924
|
+
|
925
|
+
alias size count
|
926
|
+
|
927
|
+
# Returns the index of +number+ in the set, or +nil+ if +number+ isn't in
|
928
|
+
# the set.
|
929
|
+
#
|
930
|
+
# Related: #[]
|
931
|
+
def find_index(number)
|
932
|
+
number = to_tuple_int number
|
933
|
+
each_tuple_with_index do |min, max, idx_min|
|
934
|
+
number < min and return nil
|
935
|
+
number <= max and return from_tuple_int(idx_min + (number - min))
|
936
|
+
end
|
937
|
+
nil
|
938
|
+
end
|
939
|
+
|
940
|
+
private def each_tuple_with_index
|
941
|
+
idx_min = 0
|
942
|
+
@tuples.each do |min, max|
|
943
|
+
yield min, max, idx_min, (idx_max = idx_min + (max - min))
|
944
|
+
idx_min = idx_max + 1
|
945
|
+
end
|
946
|
+
idx_min
|
947
|
+
end
|
948
|
+
|
949
|
+
private def reverse_each_tuple_with_index
|
950
|
+
idx_max = -1
|
951
|
+
@tuples.reverse_each do |min, max|
|
952
|
+
yield min, max, (idx_min = idx_max - (max - min)), idx_max
|
953
|
+
idx_max = idx_min - 1
|
954
|
+
end
|
955
|
+
idx_max
|
956
|
+
end
|
957
|
+
|
958
|
+
# :call-seq: at(index) -> integer or nil
|
959
|
+
#
|
960
|
+
# Returns a number from +self+, without modifying the set. Behaves the
|
961
|
+
# same as #[], except that #at only allows a single integer argument.
|
962
|
+
#
|
963
|
+
# Related: #[], #slice
|
964
|
+
def at(index)
|
965
|
+
index = Integer(index.to_int)
|
966
|
+
if index.negative?
|
967
|
+
reverse_each_tuple_with_index do |min, max, idx_min, idx_max|
|
968
|
+
idx_min <= index and return from_tuple_int(min + (index - idx_min))
|
969
|
+
end
|
970
|
+
else
|
971
|
+
each_tuple_with_index do |min, _, idx_min, idx_max|
|
972
|
+
index <= idx_max and return from_tuple_int(min + (index - idx_min))
|
973
|
+
end
|
974
|
+
end
|
975
|
+
nil
|
976
|
+
end
|
977
|
+
|
978
|
+
# :call-seq:
|
979
|
+
# seqset[index] -> integer or :* or nil
|
980
|
+
# slice(index) -> integer or :* or nil
|
981
|
+
# seqset[start, length] -> sequence set or nil
|
982
|
+
# slice(start, length) -> sequence set or nil
|
983
|
+
# seqset[range] -> sequence set or nil
|
984
|
+
# slice(range) -> sequence set or nil
|
985
|
+
#
|
986
|
+
# Returns a number or a subset from +self+, without modifying the set.
|
987
|
+
#
|
988
|
+
# When an Integer argument +index+ is given, the number at offset +index+
|
989
|
+
# is returned:
|
990
|
+
#
|
991
|
+
# set = Net::IMAP::SequenceSet["10:15,20:23,26"]
|
992
|
+
# set[0] #=> 10
|
993
|
+
# set[5] #=> 15
|
994
|
+
# set[10] #=> 26
|
995
|
+
#
|
996
|
+
# If +index+ is negative, it counts relative to the end of +self+:
|
997
|
+
# set = Net::IMAP::SequenceSet["10:15,20:23,26"]
|
998
|
+
# set[-1] #=> 26
|
999
|
+
# set[-3] #=> 22
|
1000
|
+
# set[-6] #=> 15
|
1001
|
+
#
|
1002
|
+
# If +index+ is out of range, +nil+ is returned.
|
1003
|
+
#
|
1004
|
+
# set = Net::IMAP::SequenceSet["10:15,20:23,26"]
|
1005
|
+
# set[11] #=> nil
|
1006
|
+
# set[-12] #=> nil
|
1007
|
+
#
|
1008
|
+
# The result is based on the normalized set—sorted and de-duplicated—not
|
1009
|
+
# on the assigned value of #string.
|
1010
|
+
#
|
1011
|
+
# set = Net::IMAP::SequenceSet["12,20:23,11:16,21"]
|
1012
|
+
# set[0] #=> 11
|
1013
|
+
# set[-1] #=> 23
|
1014
|
+
#
|
1015
|
+
def [](index, length = nil)
|
1016
|
+
if length then slice_length(index, length)
|
1017
|
+
elsif index.is_a?(Range) then slice_range(index)
|
1018
|
+
else at(index)
|
1019
|
+
end
|
1020
|
+
end
|
1021
|
+
|
1022
|
+
alias slice :[]
|
1023
|
+
|
1024
|
+
private def slice_length(start, length)
|
1025
|
+
start = Integer(start.to_int)
|
1026
|
+
length = Integer(length.to_int)
|
1027
|
+
raise ArgumentError, "length must be positive" unless length.positive?
|
1028
|
+
last = start + length - 1 unless start.negative? && start.abs <= length
|
1029
|
+
slice_range(start..last)
|
1030
|
+
end
|
1031
|
+
|
1032
|
+
private def slice_range(range)
|
1033
|
+
first = range.begin || 0
|
1034
|
+
last = range.end || -1
|
1035
|
+
last -= 1 if range.exclude_end? && range.end && last != STAR_INT
|
1036
|
+
if (first * last).positive? && last < first
|
1037
|
+
SequenceSet.empty
|
1038
|
+
elsif (min = at(first))
|
1039
|
+
max = at(last)
|
1040
|
+
if max == :* then self & (min..)
|
1041
|
+
elsif min <= max then self & (min..max)
|
1042
|
+
else SequenceSet.empty
|
1043
|
+
end
|
1044
|
+
end
|
1045
|
+
end
|
1046
|
+
|
1047
|
+
# Returns a frozen SequenceSet with <tt>*</tt> converted to +max+, numbers
|
1048
|
+
# and ranges over +max+ removed, and ranges containing +max+ converted to
|
1049
|
+
# end at +max+.
|
1050
|
+
#
|
1051
|
+
# Net::IMAP::SequenceSet["5,10:22,50"].limit(max: 20).to_s
|
1052
|
+
# #=> "5,10:20"
|
1053
|
+
#
|
1054
|
+
# <tt>*</tt> is always interpreted as the maximum value. When the set
|
1055
|
+
# contains <tt>*</tt>, it will be set equal to the limit.
|
1056
|
+
#
|
1057
|
+
# Net::IMAP::SequenceSet["*"].limit(max: 37)
|
1058
|
+
# #=> Net::IMAP::SequenceSet["37"]
|
1059
|
+
# Net::IMAP::SequenceSet["5:*"].limit(max: 37)
|
1060
|
+
# #=> Net::IMAP::SequenceSet["5:37"]
|
1061
|
+
# Net::IMAP::SequenceSet["500:*"].limit(max: 37)
|
1062
|
+
# #=> Net::IMAP::SequenceSet["37"]
|
1063
|
+
#
|
1064
|
+
def limit(max:)
|
1065
|
+
max = to_tuple_int(max)
|
1066
|
+
if empty? then self.class.empty
|
1067
|
+
elsif !include_star? && max < min then self.class.empty
|
1068
|
+
elsif max(star: STAR_INT) <= max then frozen? ? self : dup.freeze
|
1069
|
+
else dup.limit!(max: max).freeze
|
1070
|
+
end
|
1071
|
+
end
|
1072
|
+
|
1073
|
+
# Removes all members over +max+ and returns self. If <tt>*</tt> is a
|
1074
|
+
# member, it will be converted to +max+.
|
1075
|
+
#
|
1076
|
+
# Related: #limit
|
1077
|
+
def limit!(max:)
|
1078
|
+
star = include_star?
|
1079
|
+
max = to_tuple_int(max)
|
1080
|
+
tuple_subtract [max + 1, STAR_INT]
|
1081
|
+
tuple_add [max, max ] if star
|
1082
|
+
normalize!
|
1083
|
+
end
|
1084
|
+
|
1085
|
+
# :call-seq: complement! -> self
|
1086
|
+
#
|
1087
|
+
# Converts the SequenceSet to its own #complement. It will contain all
|
1088
|
+
# possible values _except_ for those currently in the set.
|
1089
|
+
#
|
1090
|
+
# Related: #complement
|
1091
|
+
def complement!
|
1092
|
+
return replace(self.class.full) if empty?
|
1093
|
+
return clear if full?
|
1094
|
+
flat = @tuples.flat_map { [_1 - 1, _2 + 1] }
|
1095
|
+
if flat.first < 1 then flat.shift else flat.unshift 1 end
|
1096
|
+
if STAR_INT < flat.last then flat.pop else flat.push STAR_INT end
|
1097
|
+
@tuples = flat.each_slice(2).to_a
|
1098
|
+
normalize!
|
1099
|
+
end
|
1100
|
+
|
1101
|
+
# Returns a new SequenceSet with a normalized string representation.
|
1102
|
+
#
|
1103
|
+
# The returned set's #string is sorted and deduplicated. Adjacent or
|
1104
|
+
# overlapping elements will be merged into a single larger range.
|
1105
|
+
#
|
1106
|
+
# Net::IMAP::SequenceSet["1:5,3:7,10:9,10:11"].normalize
|
1107
|
+
# #=> Net::IMAP::SequenceSet["1:7,9:11"]
|
1108
|
+
#
|
1109
|
+
# Related: #normalize!, #normalized_string
|
1110
|
+
def normalize
|
1111
|
+
str = normalized_string
|
1112
|
+
return self if frozen? && str == string
|
1113
|
+
remain_frozen dup.instance_exec { @string = str&.-@; self }
|
1114
|
+
end
|
1115
|
+
|
1116
|
+
# Resets #string to be sorted, deduplicated, and coalesced. Returns
|
1117
|
+
# +self+.
|
1118
|
+
#
|
1119
|
+
# Related: #normalize, #normalized_string
|
1120
|
+
def normalize!
|
1121
|
+
@string = nil
|
1122
|
+
self
|
1123
|
+
end
|
1124
|
+
|
1125
|
+
# Returns a normalized +sequence-set+ string representation, sorted
|
1126
|
+
# and deduplicated. Adjacent or overlapping elements will be merged into
|
1127
|
+
# a single larger range. Returns +nil+ when the set is empty.
|
1128
|
+
#
|
1129
|
+
# Net::IMAP::SequenceSet["1:5,3:7,10:9,10:11"].normalized_string
|
1130
|
+
# #=> "1:7,9:11"
|
1131
|
+
#
|
1132
|
+
# Related: #normalize!, #normalize
|
1133
|
+
def normalized_string
|
1134
|
+
@tuples.empty? ? nil : -@tuples.map { tuple_to_str _1 }.join(",")
|
1135
|
+
end
|
48
1136
|
|
49
1137
|
def inspect
|
50
|
-
|
1138
|
+
if empty?
|
1139
|
+
(frozen? ? "%s.empty" : "#<%s empty>") % [self.class]
|
1140
|
+
elsif frozen?
|
1141
|
+
"%s[%p]" % [self.class, to_s]
|
1142
|
+
else
|
1143
|
+
"#<%s %p>" % [self.class, to_s]
|
1144
|
+
end
|
51
1145
|
end
|
52
1146
|
|
53
|
-
#
|
1147
|
+
# Returns self
|
1148
|
+
alias to_sequence_set itself
|
1149
|
+
|
1150
|
+
# Unstable API: currently for internal use only (Net::IMAP#validate_data)
|
54
1151
|
def validate # :nodoc:
|
55
|
-
|
56
|
-
|
57
|
-
true
|
1152
|
+
empty? and raise DataFormatError, "empty sequence-set is invalid"
|
1153
|
+
self
|
58
1154
|
end
|
59
1155
|
|
60
|
-
# Unstable API
|
1156
|
+
# Unstable API: for internal use only (Net::IMAP#send_data)
|
61
1157
|
def send_data(imap, tag) # :nodoc:
|
62
|
-
imap.__send__(:put_string,
|
1158
|
+
imap.__send__(:put_string, valid_string)
|
1159
|
+
end
|
1160
|
+
|
1161
|
+
protected
|
1162
|
+
|
1163
|
+
attr_reader :tuples # :nodoc:
|
1164
|
+
|
1165
|
+
private
|
1166
|
+
|
1167
|
+
def remain_frozen(set) frozen? ? set.freeze : set end
|
1168
|
+
|
1169
|
+
# frozen clones are shallow copied
|
1170
|
+
def initialize_clone(other)
|
1171
|
+
other.frozen? ? super : initialize_dup(other)
|
63
1172
|
end
|
64
1173
|
|
1174
|
+
def initialize_dup(other)
|
1175
|
+
@tuples = other.tuples.map(&:dup)
|
1176
|
+
@string = other.string&.-@
|
1177
|
+
super
|
1178
|
+
end
|
1179
|
+
|
1180
|
+
def input_to_tuple(obj)
|
1181
|
+
obj = input_try_convert obj
|
1182
|
+
case obj
|
1183
|
+
when *STARS, Integer then [int = to_tuple_int(obj), int]
|
1184
|
+
when Range then range_to_tuple(obj)
|
1185
|
+
when String then str_to_tuple(obj)
|
1186
|
+
else
|
1187
|
+
raise DataFormatError, "expected number or range, got %p" % [obj]
|
1188
|
+
end
|
1189
|
+
end
|
1190
|
+
|
1191
|
+
def input_to_tuples(obj)
|
1192
|
+
obj = input_try_convert obj
|
1193
|
+
case obj
|
1194
|
+
when *STARS, Integer, Range then [input_to_tuple(obj)]
|
1195
|
+
when String then str_to_tuples obj
|
1196
|
+
when SequenceSet then obj.tuples
|
1197
|
+
when ENUMABLE then obj.flat_map { input_to_tuples _1 }
|
1198
|
+
when nil then []
|
1199
|
+
else
|
1200
|
+
raise DataFormatError,
|
1201
|
+
"expected nz-number, range, string, or enumerable; " \
|
1202
|
+
"got %p" % [obj]
|
1203
|
+
end
|
1204
|
+
end
|
1205
|
+
|
1206
|
+
# unlike SequenceSet#try_convert, this returns an Integer, Range,
|
1207
|
+
# String, Set, Array, or... any type of object.
|
1208
|
+
def input_try_convert(input)
|
1209
|
+
SequenceSet.try_convert(input) ||
|
1210
|
+
# Integer.try_convert(input) || # ruby 3.1+
|
1211
|
+
input.respond_to?(:to_int) && Integer(input.to_int) ||
|
1212
|
+
String.try_convert(input) ||
|
1213
|
+
input
|
1214
|
+
end
|
1215
|
+
|
1216
|
+
def range_to_tuple(range)
|
1217
|
+
first = to_tuple_int(range.begin || 1)
|
1218
|
+
last = to_tuple_int(range.end || :*)
|
1219
|
+
last -= 1 if range.exclude_end? && range.end && last != STAR_INT
|
1220
|
+
unless first <= last
|
1221
|
+
raise DataFormatError, "invalid range for sequence-set: %p" % [range]
|
1222
|
+
end
|
1223
|
+
[first, last]
|
1224
|
+
end
|
1225
|
+
|
1226
|
+
def to_tuple_int(obj) STARS.include?(obj) ? STAR_INT : nz_number(obj) end
|
1227
|
+
def from_tuple_int(num) num == STAR_INT ? :* : num end
|
1228
|
+
|
1229
|
+
def tuple_to_str(tuple) tuple.uniq.map{ from_tuple_int _1 }.join(":") end
|
1230
|
+
def str_to_tuples(str) str.split(",", -1).map! { str_to_tuple _1 } end
|
1231
|
+
def str_to_tuple(str)
|
1232
|
+
raise DataFormatError, "invalid sequence set string" if str.empty?
|
1233
|
+
str.split(":", 2).map! { to_tuple_int _1 }.minmax
|
1234
|
+
end
|
1235
|
+
|
1236
|
+
def include_tuple?((min, max)) range_gte_to(min)&.cover?(min..max) end
|
1237
|
+
|
1238
|
+
def intersect_tuple?((min, max))
|
1239
|
+
range = range_gte_to(min) and
|
1240
|
+
range.include?(min) || range.include?(max) || (min..max).cover?(range)
|
1241
|
+
end
|
1242
|
+
|
1243
|
+
def tuples_add(tuples) tuples.each do tuple_add _1 end; self end
|
1244
|
+
def tuples_subtract(tuples) tuples.each do tuple_subtract _1 end; self end
|
1245
|
+
|
1246
|
+
#
|
1247
|
+
# --|=====| |=====new tuple=====| append
|
1248
|
+
# ?????????-|=====new tuple=====|-|===lower===|-- insert
|
1249
|
+
#
|
1250
|
+
# |=====new tuple=====|
|
1251
|
+
# ---------??=======lower=======??--------------- noop
|
1252
|
+
#
|
1253
|
+
# ---------??===lower==|--|==| join remaining
|
1254
|
+
# ---------??===lower==|--|==|----|===upper===|-- join until upper
|
1255
|
+
# ---------??===lower==|--|==|--|=====upper===|-- join to upper
|
1256
|
+
def tuple_add(tuple)
|
1257
|
+
min, max = tuple
|
1258
|
+
lower, lower_idx = tuple_gte_with_index(min - 1)
|
1259
|
+
if lower.nil? then tuples << tuple
|
1260
|
+
elsif (max + 1) < lower.first then tuples.insert(lower_idx, tuple)
|
1261
|
+
else tuple_coalesce(lower, lower_idx, min, max)
|
1262
|
+
end
|
1263
|
+
end
|
1264
|
+
|
1265
|
+
def tuple_coalesce(lower, lower_idx, min, max)
|
1266
|
+
return if lower.first <= min && max <= lower.last
|
1267
|
+
lower[0] = [min, lower.first].min
|
1268
|
+
lower[1] = [max, lower.last].max
|
1269
|
+
lower_idx += 1
|
1270
|
+
return if lower_idx == tuples.count
|
1271
|
+
tmax_adj = lower.last + 1
|
1272
|
+
upper, upper_idx = tuple_gte_with_index(tmax_adj)
|
1273
|
+
if upper
|
1274
|
+
tmax_adj < upper.first ? (upper_idx -= 1) : (lower[1] = upper.last)
|
1275
|
+
end
|
1276
|
+
tuples.slice!(lower_idx..upper_idx)
|
1277
|
+
end
|
1278
|
+
|
1279
|
+
# |====tuple================|
|
1280
|
+
# --|====| no more 1. noop
|
1281
|
+
# --|====|---------------------------|====lower====|-- 2. noop
|
1282
|
+
# -------|======lower================|---------------- 3. split
|
1283
|
+
# --------|=====lower================|---------------- 4. trim beginning
|
1284
|
+
#
|
1285
|
+
# -------|======lower====????????????----------------- trim lower
|
1286
|
+
# --------|=====lower====????????????----------------- delete lower
|
1287
|
+
#
|
1288
|
+
# -------??=====lower===============|----------------- 5. trim/delete one
|
1289
|
+
# -------??=====lower====|--|====| no more 6. delete rest
|
1290
|
+
# -------??=====lower====|--|====|---|====upper====|-- 7. delete until
|
1291
|
+
# -------??=====lower====|--|====|--|=====upper====|-- 8. delete and trim
|
1292
|
+
def tuple_subtract(tuple)
|
1293
|
+
min, max = tuple
|
1294
|
+
lower, idx = tuple_gte_with_index(min)
|
1295
|
+
if lower.nil? then nil # case 1.
|
1296
|
+
elsif max < lower.first then nil # case 2.
|
1297
|
+
elsif max < lower.last then tuple_trim_or_split lower, idx, min, max
|
1298
|
+
else tuples_trim_or_delete lower, idx, min, max
|
1299
|
+
end
|
1300
|
+
end
|
1301
|
+
|
1302
|
+
def tuple_trim_or_split(lower, idx, tmin, tmax)
|
1303
|
+
if lower.first < tmin # split
|
1304
|
+
tuples.insert(idx, [lower.first, tmin - 1])
|
1305
|
+
end
|
1306
|
+
lower[0] = tmax + 1
|
1307
|
+
end
|
1308
|
+
|
1309
|
+
def tuples_trim_or_delete(lower, lower_idx, tmin, tmax)
|
1310
|
+
if lower.first < tmin # trim lower
|
1311
|
+
lower[1] = tmin - 1
|
1312
|
+
lower_idx += 1
|
1313
|
+
end
|
1314
|
+
if tmax == lower.last # case 5
|
1315
|
+
upper_idx = lower_idx
|
1316
|
+
elsif (upper, upper_idx = tuple_gte_with_index(tmax + 1))
|
1317
|
+
upper_idx -= 1 # cases 7 and 8
|
1318
|
+
upper[0] = tmax + 1 if upper.first <= tmax # case 8 (else case 7)
|
1319
|
+
end
|
1320
|
+
tuples.slice!(lower_idx..upper_idx)
|
1321
|
+
end
|
1322
|
+
|
1323
|
+
def tuple_gte_with_index(num)
|
1324
|
+
idx = tuples.bsearch_index { _2 >= num } and [tuples[idx], idx]
|
1325
|
+
end
|
1326
|
+
|
1327
|
+
def range_gte_to(num)
|
1328
|
+
first, last = tuples.bsearch { _2 >= num }
|
1329
|
+
first..last if first
|
1330
|
+
end
|
1331
|
+
|
1332
|
+
def nz_number(num)
|
1333
|
+
case num
|
1334
|
+
when Integer, /\A[1-9]\d*\z/ then num = Integer(num)
|
1335
|
+
else raise DataFormatError, "%p is not a valid nz-number" % [num]
|
1336
|
+
end
|
1337
|
+
NumValidator.ensure_nz_number(num)
|
1338
|
+
num
|
1339
|
+
end
|
1340
|
+
|
1341
|
+
# intentionally defined after the class implementation
|
1342
|
+
|
1343
|
+
EMPTY = new.freeze
|
1344
|
+
FULL = self["1:*"]
|
1345
|
+
private_constant :EMPTY, :FULL
|
1346
|
+
|
65
1347
|
end
|
66
1348
|
end
|
67
1349
|
end
|