net-imap 0.5.8 → 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 +4 -4
- data/.document +3 -0
- data/.rdoc_options +7 -0
- data/Gemfile +8 -5
- data/README.md +1 -1
- data/lib/net/imap/command_data.rb +170 -80
- data/lib/net/imap/config/attr_accessors.rb +8 -9
- data/lib/net/imap/config/attr_inheritance.rb +64 -1
- data/lib/net/imap/config/attr_type_coercion.rb +18 -6
- data/lib/net/imap/config/attr_version_defaults.rb +90 -0
- data/lib/net/imap/config.rb +244 -122
- data/lib/net/imap/connection_state.rb +1 -1
- data/lib/net/imap/data_encoding.rb +126 -27
- data/lib/net/imap/errors.rb +189 -0
- data/lib/net/imap/esearch_result.rb +48 -3
- data/lib/net/imap/flags.rb +1 -1
- data/lib/net/imap/response_data.rb +110 -15
- data/lib/net/imap/response_parser/parser_utils.rb +14 -23
- data/lib/net/imap/response_parser.rb +40 -17
- data/lib/net/imap/response_reader.rb +25 -16
- data/lib/net/imap/sasl/scram_authenticator.rb +74 -0
- data/lib/net/imap/search_result.rb +13 -4
- data/lib/net/imap/sequence_set.rb +936 -397
- data/lib/net/imap/uidplus_data.rb +2 -63
- data/lib/net/imap/vanished_data.rb +10 -1
- data/lib/net/imap.rb +245 -100
- data/net-imap.gemspec +1 -1
- data/rakelib/rdoc.rake +1 -18
- data/rakelib/string_prep_tables_generator.rb +4 -2
- metadata +6 -4
- data/lib/net/imap/data_lite.rb +0 -226
|
@@ -18,21 +18,9 @@ module Net
|
|
|
18
18
|
#
|
|
19
19
|
# == Creating sequence sets
|
|
20
20
|
#
|
|
21
|
-
# SequenceSet.new with no arguments creates an empty sequence set. Note
|
|
22
|
-
# that an empty sequence set is invalid in the \IMAP grammar.
|
|
23
|
-
#
|
|
24
|
-
# set = Net::IMAP::SequenceSet.new
|
|
25
|
-
# set.empty? #=> true
|
|
26
|
-
# set.valid? #=> false
|
|
27
|
-
# set.valid_string #!> raises DataFormatError
|
|
28
|
-
# set << 1..10
|
|
29
|
-
# set.empty? #=> false
|
|
30
|
-
# set.valid? #=> true
|
|
31
|
-
# set.valid_string #=> "1:10"
|
|
32
|
-
#
|
|
33
21
|
# SequenceSet.new may receive a single optional argument: a non-zero 32 bit
|
|
34
22
|
# unsigned integer, a range, a <tt>sequence-set</tt> formatted string,
|
|
35
|
-
# another
|
|
23
|
+
# another SequenceSet, a Set (containing only numbers or <tt>*</tt>), or an
|
|
36
24
|
# Array containing any of these (array inputs may be nested).
|
|
37
25
|
#
|
|
38
26
|
# set = Net::IMAP::SequenceSet.new(1)
|
|
@@ -48,30 +36,118 @@ module Net
|
|
|
48
36
|
# set = Net::IMAP::SequenceSet.new(1, 2, 3..7, 5, 6..10, 2048, 1024)
|
|
49
37
|
# set.valid_string #=> "1:10,55,1024:2048"
|
|
50
38
|
#
|
|
51
|
-
#
|
|
52
|
-
#
|
|
39
|
+
# SequenceSet.new with no arguments creates an empty sequence set. Note
|
|
40
|
+
# that an empty sequence set is invalid in the \IMAP grammar.
|
|
41
|
+
#
|
|
42
|
+
# set = Net::IMAP::SequenceSet.new
|
|
43
|
+
# set.empty? #=> true
|
|
44
|
+
# set.valid? #=> false
|
|
45
|
+
# set.valid_string #!> raises DataFormatError
|
|
46
|
+
# set << 1..10
|
|
47
|
+
# set.empty? #=> false
|
|
48
|
+
# set.valid? #=> true
|
|
49
|
+
# set.valid_string #=> "1:10"
|
|
50
|
+
#
|
|
51
|
+
# Using SequenceSet.new with another SequenceSet input behaves the same as
|
|
52
|
+
# calling #dup on the other set. The input's #string will be preserved.
|
|
53
|
+
#
|
|
54
|
+
# input = Net::IMAP::SequenceSet.new("1,2,3:7,5,6:10,2048,1024")
|
|
55
|
+
# copy = Net::IMAP::SequenceSet.new(input)
|
|
56
|
+
# input.valid_string #=> "1,2,3:7,5,6:10,2048,1024"
|
|
57
|
+
# copy.valid_string #=> "1,2,3:7,5,6:10,2048,1024"
|
|
58
|
+
# copy2 = input.dup # same as calling new with a SequenceSet input
|
|
59
|
+
# copy == input #=> true, same set membership
|
|
60
|
+
# copy.eql? input #=> true, same string value
|
|
61
|
+
# copy.equal? input #=> false, different objects
|
|
62
|
+
#
|
|
63
|
+
# copy.normalize!
|
|
64
|
+
# copy.valid_string #=> "1:10,1024,2048"
|
|
65
|
+
# copy == input #=> true, same set membership
|
|
66
|
+
# copy.eql? input #=> false, different string value
|
|
67
|
+
#
|
|
68
|
+
# copy << 999
|
|
69
|
+
# copy.valid_string #=> "1:10,999,1024,2048"
|
|
70
|
+
# copy == input #=> false, different set membership
|
|
71
|
+
# copy.eql? input #=> false, different string value
|
|
72
|
+
#
|
|
73
|
+
# Use Net::IMAP::SequenceSet() to coerce a single (optional) input.
|
|
74
|
+
# A SequenceSet input is returned without duplication, even when frozen.
|
|
75
|
+
#
|
|
76
|
+
# set = Net::IMAP::SequenceSet()
|
|
77
|
+
# set.string #=> nil
|
|
78
|
+
# set.frozen? #=> false
|
|
79
|
+
#
|
|
80
|
+
# # String order is preserved
|
|
81
|
+
# set = Net::IMAP::SequenceSet("1,2,3:7,5,6:10,2048,1024")
|
|
82
|
+
# set.valid_string #=> "1,2,3:7,5,6:10,2048,1024"
|
|
83
|
+
# set.frozen? #=> false
|
|
84
|
+
#
|
|
85
|
+
# # Other inputs are normalized
|
|
86
|
+
# set = Net::IMAP::SequenceSet([1, 2, [3..7, 5], 6..10, 2048, 1024])
|
|
87
|
+
# set.valid_string #=> "1:10,1024,2048"
|
|
88
|
+
# set.frozen? #=> false
|
|
53
89
|
#
|
|
90
|
+
# unfrozen = set
|
|
91
|
+
# frozen = set.dup.freeze
|
|
92
|
+
# unfrozen.equal? Net::IMAP::SequenceSet(unfrozen) #=> true
|
|
93
|
+
# frozen.equal? Net::IMAP::SequenceSet(frozen) #=> true
|
|
94
|
+
#
|
|
95
|
+
# Use ::[] to coerce one or more arguments into a valid frozen SequenceSet.
|
|
96
|
+
# A valid frozen SequenceSet is returned directly, without allocating a new
|
|
97
|
+
# object. ::[] will not create an invalid (empty) set.
|
|
98
|
+
#
|
|
99
|
+
# Net::IMAP::SequenceSet[] #!> raises ArgumentError
|
|
100
|
+
# Net::IMAP::SequenceSet[nil] #!> raises DataFormatError
|
|
101
|
+
# Net::IMAP::SequenceSet[""] #!> raises DataFormatError
|
|
102
|
+
#
|
|
103
|
+
# # String order is preserved
|
|
54
104
|
# set = Net::IMAP::SequenceSet["1,2,3:7,5,6:10,2048,1024"]
|
|
55
105
|
# set.valid_string #=> "1,2,3:7,5,6:10,2048,1024"
|
|
106
|
+
# set.frozen? #=> true
|
|
107
|
+
#
|
|
108
|
+
# # Other inputs are normalized
|
|
56
109
|
# set = Net::IMAP::SequenceSet[1, 2, [3..7, 5], 6..10, 2048, 1024]
|
|
57
|
-
# set.valid_string #=> "1:10,
|
|
110
|
+
# set.valid_string #=> "1:10,1024,2048"
|
|
111
|
+
# set.frozen? #=> true
|
|
112
|
+
#
|
|
113
|
+
# frozen = set
|
|
114
|
+
# unfrozen = set.dup
|
|
115
|
+
# frozen.equal? Net::IMAP::SequenceSet[frozen] #=> true
|
|
116
|
+
# unfrozen.equal? Net::IMAP::SequenceSet[unfrozen] #=> false
|
|
117
|
+
#
|
|
118
|
+
# Objects which respond to +to_sequence_set+ (such as SearchResult and
|
|
119
|
+
# ThreadMember) can be coerced to a SequenceSet with ::new, ::try_convert,
|
|
120
|
+
# ::[], or Net::IMAP::SequenceSet.
|
|
121
|
+
#
|
|
122
|
+
# search = imap.uid_search(["SUBJECT", "hello", "NOT", "SEEN"])
|
|
123
|
+
# seqset = Net::IMAP::SequenceSet(search) - already_fetched
|
|
124
|
+
# fetch = imap.uid_fetch(seqset, "FAST")
|
|
58
125
|
#
|
|
59
126
|
# == Ordered and Normalized sets
|
|
60
127
|
#
|
|
61
128
|
# Sometimes the order of the set's members is significant, such as with the
|
|
62
129
|
# +ESORT+, <tt>CONTEXT=SORT</tt>, and +UIDPLUS+ extensions. So, when a
|
|
63
|
-
# sequence set is created
|
|
64
|
-
# #string representation is preserved.
|
|
130
|
+
# sequence set is created from a single string (such as by the parser), that
|
|
131
|
+
# #string representation is preserved. Assigning a string with #string= or
|
|
132
|
+
# #replace will also preserve that string. Use #each_entry, #entries, or
|
|
133
|
+
# #each_ordered_number to enumerate the entries in their #string order.
|
|
134
|
+
# Hash equality (using #eql?) is based on the string representation.
|
|
65
135
|
#
|
|
66
|
-
# Internally, SequenceSet
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
# the set in
|
|
136
|
+
# Internally, SequenceSet uses a normalized uint32 set representation which
|
|
137
|
+
# sorts and de-duplicates all numbers and coalesces adjacent or overlapping
|
|
138
|
+
# entries. Many methods use this sorted set representation for <tt>O(lg
|
|
139
|
+
# n)</tt> searches. Use #each_element, #elements, #each_range, #ranges,
|
|
140
|
+
# #each_number, or #numbers to enumerate the set in sorted order. Basic
|
|
141
|
+
# object equality (using #==) is based on set membership, without regard to
|
|
142
|
+
# #entry order or #string normalization.
|
|
71
143
|
#
|
|
72
|
-
# Most modification methods
|
|
73
|
-
#
|
|
74
|
-
#
|
|
144
|
+
# Most modification methods reset #string to its #normalized form, so that
|
|
145
|
+
# #entries and #elements are identical. Use #append to preserve #entries
|
|
146
|
+
# order while modifying a set.
|
|
147
|
+
#
|
|
148
|
+
# Non-normalized sets store both representations of the set, which can more
|
|
149
|
+
# than double memory usage. Very large sequence sets should avoid
|
|
150
|
+
# denormalizing methods (such as #append) unless order is significant.
|
|
75
151
|
#
|
|
76
152
|
# == Using <tt>*</tt>
|
|
77
153
|
#
|
|
@@ -107,7 +183,7 @@ module Net
|
|
|
107
183
|
#
|
|
108
184
|
# When a set includes <tt>*</tt>, some methods may have surprising behavior.
|
|
109
185
|
#
|
|
110
|
-
# For example, #complement treats <tt>*</tt> as its own
|
|
186
|
+
# For example, #complement treats <tt>*</tt> as its own member. This way,
|
|
111
187
|
# the #intersection of a set and its #complement will always be empty. And
|
|
112
188
|
# <tt>*</tt> is sorted as greater than any other number in the set. This is
|
|
113
189
|
# not how an \IMAP server interprets the set: it will convert <tt>*</tt> to
|
|
@@ -127,7 +203,7 @@ module Net
|
|
|
127
203
|
# (set.limit(max: 4) & (~set).limit(max: 4)).to_a => [4]
|
|
128
204
|
#
|
|
129
205
|
# When counting the number of numbers in a set, <tt>*</tt> will be counted
|
|
130
|
-
#
|
|
206
|
+
# as if it were equal to UINT32_MAX:
|
|
131
207
|
# UINT32_MAX = 2**32 - 1
|
|
132
208
|
# Net::IMAP::SequenceSet["*"].count => 1
|
|
133
209
|
# Net::IMAP::SequenceSet[1..UINT32_MAX - 1, :*].count => UINT32_MAX
|
|
@@ -136,6 +212,12 @@ module Net
|
|
|
136
212
|
# Net::IMAP::SequenceSet[UINT32_MAX, :*].count => 1
|
|
137
213
|
# Net::IMAP::SequenceSet[UINT32_MAX..].count => 1
|
|
138
214
|
#
|
|
215
|
+
# Use #cardinality to count the set members wxth <tt>*</tt> counted as a
|
|
216
|
+
# distinct member:
|
|
217
|
+
# Net::IMAP::SequenceSet[1..].cardinality #=> UINT32_MAX + 1
|
|
218
|
+
# Net::IMAP::SequenceSet[UINT32_MAX, :*].cardinality #=> 2
|
|
219
|
+
# Net::IMAP::SequenceSet[UINT32_MAX..].cardinality #=> 2
|
|
220
|
+
#
|
|
139
221
|
# == What's here?
|
|
140
222
|
#
|
|
141
223
|
# SequenceSet provides methods for:
|
|
@@ -153,6 +235,7 @@ module Net
|
|
|
153
235
|
# * ::new: Creates a new mutable sequence set, which may be empty (invalid).
|
|
154
236
|
# * ::try_convert: Calls +to_sequence_set+ on an object and verifies that
|
|
155
237
|
# the result is a SequenceSet.
|
|
238
|
+
# * Net::IMAP::SequenceSet(): Coerce an input using ::try_convert or ::new.
|
|
156
239
|
# * ::empty: Returns a frozen empty (invalid) SequenceSet.
|
|
157
240
|
# * ::full: Returns a frozen SequenceSet containing every possible number.
|
|
158
241
|
#
|
|
@@ -178,8 +261,7 @@ module Net
|
|
|
178
261
|
#
|
|
179
262
|
# <i>Set membership:</i>
|
|
180
263
|
# - #include? (aliased as #member?):
|
|
181
|
-
# Returns whether a given element
|
|
182
|
-
# contained by the set.
|
|
264
|
+
# Returns whether a given element is contained by the set.
|
|
183
265
|
# - #include_star?: Returns whether the set contains <tt>*</tt>.
|
|
184
266
|
#
|
|
185
267
|
# <i>Minimum and maximum value elements:</i>
|
|
@@ -199,8 +281,10 @@ module Net
|
|
|
199
281
|
# occurrence in entries.
|
|
200
282
|
#
|
|
201
283
|
# <i>Set cardinality:</i>
|
|
202
|
-
# - #
|
|
203
|
-
#
|
|
284
|
+
# - #cardinality: Returns the number of distinct members in the set.
|
|
285
|
+
# <tt>*</tt> is counted as its own member, distinct from UINT32_MAX.
|
|
286
|
+
# - #count: Returns the count of distinct numbers in the set.
|
|
287
|
+
# <tt>*</tt> is counted as equal to UINT32_MAX.
|
|
204
288
|
# - #empty?: Returns whether the set has no members. \IMAP syntax does not
|
|
205
289
|
# allow empty sequence sets.
|
|
206
290
|
# - #valid?: Returns whether the set has any members.
|
|
@@ -208,12 +292,18 @@ module Net
|
|
|
208
292
|
# <tt>*</tt>.
|
|
209
293
|
#
|
|
210
294
|
# <i>Denormalized properties:</i>
|
|
295
|
+
# - #normalized?: Returns whether #entries are sorted, deduplicated, and
|
|
296
|
+
# coalesced, and all #string entries are in normalized form.
|
|
211
297
|
# - #has_duplicates?: Returns whether the ordered entries repeat any
|
|
212
298
|
# numbers.
|
|
213
|
-
# - #
|
|
214
|
-
#
|
|
299
|
+
# - #size: Returns the total size of all #entries, including repeated
|
|
300
|
+
# numbers. <tt>*</tt> is counted as its own member, distinct from
|
|
301
|
+
# UINT32_MAX.
|
|
215
302
|
# - #count_with_duplicates: Returns the count of numbers in the ordered
|
|
216
|
-
# entries, including
|
|
303
|
+
# #entries, including repeated numbers. <tt>*</tt> is counted as
|
|
304
|
+
# equal to UINT32_MAX.
|
|
305
|
+
# - #count_duplicates: Returns the count of repeated numbers in the ordered
|
|
306
|
+
# #entries. <tt>*</tt> is counted as equal to UINT32_MAX.
|
|
217
307
|
#
|
|
218
308
|
# === Methods for Iterating
|
|
219
309
|
#
|
|
@@ -260,7 +350,7 @@ module Net
|
|
|
260
350
|
# given maximum value and removed all members over that maximum.
|
|
261
351
|
#
|
|
262
352
|
# === Methods for Assigning
|
|
263
|
-
# These methods add or replace
|
|
353
|
+
# These methods add or replace numbers in +self+.
|
|
264
354
|
#
|
|
265
355
|
# <i>Normalized (sorted and coalesced):</i>
|
|
266
356
|
#
|
|
@@ -269,8 +359,12 @@ module Net
|
|
|
269
359
|
# - #add (aliased as #<<): Adds a given element to the set; returns +self+.
|
|
270
360
|
# - #add?: If the given element is not fully included the set, adds it and
|
|
271
361
|
# returns +self+; otherwise, returns +nil+.
|
|
272
|
-
# - #merge: Adds all members of the given sets into
|
|
273
|
-
#
|
|
362
|
+
# - #merge: In-place set #union. Adds all members of the given sets into
|
|
363
|
+
# this set; returns +self+.
|
|
364
|
+
# - #complement!: In-place set #complement. Replaces the contents of this
|
|
365
|
+
# set with its own #complement; returns +self+.
|
|
366
|
+
# - #xor!: In-place +XOR+ operation. Adds numbers that are unique to the
|
|
367
|
+
# other set and removes numbers that are common to both; returns +self+.
|
|
274
368
|
#
|
|
275
369
|
# <i>Order preserving:</i>
|
|
276
370
|
#
|
|
@@ -283,7 +377,7 @@ module Net
|
|
|
283
377
|
# of a given object.
|
|
284
378
|
#
|
|
285
379
|
# === Methods for Deleting
|
|
286
|
-
# These methods remove
|
|
380
|
+
# These methods remove numbers from +self+, and update #string to be fully
|
|
287
381
|
# sorted and coalesced.
|
|
288
382
|
#
|
|
289
383
|
# - #clear: Removes all elements in the set; returns +self+.
|
|
@@ -291,10 +385,12 @@ module Net
|
|
|
291
385
|
# - #delete?: If the given element is included in the set, removes it and
|
|
292
386
|
# returns it; otherwise, returns +nil+.
|
|
293
387
|
# - #delete_at: Removes the number at a given offset.
|
|
388
|
+
# - #intersect!: In-place set #intersection. Removes numbers that are not
|
|
389
|
+
# in the given set; returns +self+.
|
|
294
390
|
# - #slice!: Removes the number or consecutive numbers at a given offset or
|
|
295
391
|
# range of offsets.
|
|
296
|
-
# - #subtract: Removes all members of the given
|
|
297
|
-
# +self+.
|
|
392
|
+
# - #subtract: In-place set #difference. Removes all members of the given
|
|
393
|
+
# sets from this set; returns +self+.
|
|
298
394
|
# - #limit!: Replaces <tt>*</tt> with a given maximum value and removes all
|
|
299
395
|
# members over that maximum; returns +self+.
|
|
300
396
|
#
|
|
@@ -324,6 +420,23 @@ module Net
|
|
|
324
420
|
STARS = [:*, ?*, -1].freeze
|
|
325
421
|
private_constant :STARS
|
|
326
422
|
|
|
423
|
+
INSPECT_MAX_LEN = 512
|
|
424
|
+
INSPECT_TRUNCATE_LEN = 16
|
|
425
|
+
private_constant :INSPECT_MAX_LEN, :INSPECT_TRUNCATE_LEN
|
|
426
|
+
|
|
427
|
+
# /(,\d+){100}\z/ is shockingly slow on huge strings.
|
|
428
|
+
# /(,\d{0,10}){100}\z/ is ok, but ironically, Regexp.linear_time? is false.
|
|
429
|
+
#
|
|
430
|
+
# This unrolls all nested quantifiers. It's much harder to read, but it's
|
|
431
|
+
# also the fastest out of all the versions I tested.
|
|
432
|
+
nz_uint32 = /[1-9](?:\d(?:\d(?:\d(?:\d(?:\d(?:\d(?:\d(?:\d(?:\d)?)?)?)?)?)?)?)?)?/
|
|
433
|
+
num_or_star = /#{nz_uint32}|\*/
|
|
434
|
+
entry = /#{num_or_star}(?::#{num_or_star})?/
|
|
435
|
+
entries = ([entry] * INSPECT_TRUNCATE_LEN).join(",")
|
|
436
|
+
INSPECT_ABRIDGED_HEAD_RE = /\A#{entries},/
|
|
437
|
+
INSPECT_ABRIDGED_TAIL_RE = /,#{entries}\z/
|
|
438
|
+
private_constant :INSPECT_ABRIDGED_HEAD_RE, :INSPECT_ABRIDGED_TAIL_RE
|
|
439
|
+
|
|
327
440
|
class << self
|
|
328
441
|
|
|
329
442
|
# :call-seq:
|
|
@@ -337,13 +450,12 @@ module Net
|
|
|
337
450
|
# An empty SequenceSet is invalid and will raise a DataFormatError.
|
|
338
451
|
#
|
|
339
452
|
# Use ::new to create a mutable or empty SequenceSet.
|
|
453
|
+
#
|
|
454
|
+
# Related: ::new, Net::IMAP::SequenceSet(), ::try_convert
|
|
340
455
|
def [](first, *rest)
|
|
341
456
|
if rest.empty?
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
else
|
|
345
|
-
new(first).validate.freeze
|
|
346
|
-
end
|
|
457
|
+
set = try_convert(first)&.validate
|
|
458
|
+
set&.frozen? ? set : (set&.dup || new(first).validate).freeze
|
|
347
459
|
else
|
|
348
460
|
new(first).merge(*rest).validate.freeze
|
|
349
461
|
end
|
|
@@ -356,12 +468,14 @@ module Net
|
|
|
356
468
|
# +to_sequence_set+, calls +obj.to_sequence_set+ and returns the result.
|
|
357
469
|
# Otherwise returns +nil+.
|
|
358
470
|
#
|
|
359
|
-
# If +obj.to_sequence_set+ doesn't return a SequenceSet
|
|
360
|
-
# raised.
|
|
471
|
+
# If +obj.to_sequence_set+ doesn't return a SequenceSet or +nil+, an
|
|
472
|
+
# exception is raised.
|
|
473
|
+
#
|
|
474
|
+
# Related: Net::IMAP::SequenceSet(), ::new, ::[]
|
|
361
475
|
def try_convert(obj)
|
|
362
476
|
return obj if obj.is_a?(SequenceSet)
|
|
363
477
|
return nil unless obj.respond_to?(:to_sequence_set)
|
|
364
|
-
obj = obj.to_sequence_set
|
|
478
|
+
return nil unless obj = obj.to_sequence_set
|
|
365
479
|
return obj if obj.is_a?(SequenceSet)
|
|
366
480
|
raise DataFormatError, "invalid object returned from to_sequence_set"
|
|
367
481
|
end
|
|
@@ -376,23 +490,96 @@ module Net
|
|
|
376
490
|
end
|
|
377
491
|
|
|
378
492
|
# Create a new SequenceSet object from +input+, which may be another
|
|
379
|
-
# SequenceSet, an IMAP formatted +sequence-set+ string, a
|
|
380
|
-
# range, <tt>:*</tt>,
|
|
381
|
-
#
|
|
382
|
-
#
|
|
383
|
-
|
|
493
|
+
# SequenceSet, an IMAP formatted +sequence-set+ string, a non-zero 32 bit
|
|
494
|
+
# unsigned integer, a range, <tt>:*</tt>, a Set of numbers or <tt>*</tt>,
|
|
495
|
+
# an object that responds to +to_sequence_set+ (such as SearchResult) or
|
|
496
|
+
# an Array of these (array inputs may be nested).
|
|
497
|
+
#
|
|
498
|
+
# set = Net::IMAP::SequenceSet.new(1)
|
|
499
|
+
# set.valid_string #=> "1"
|
|
500
|
+
# set = Net::IMAP::SequenceSet.new(1..100)
|
|
501
|
+
# set.valid_string #=> "1:100"
|
|
502
|
+
# set = Net::IMAP::SequenceSet.new(1...100)
|
|
503
|
+
# set.valid_string #=> "1:99"
|
|
504
|
+
# set = Net::IMAP::SequenceSet.new([1, 2, 5..])
|
|
505
|
+
# set.valid_string #=> "1:2,5:*"
|
|
506
|
+
# set = Net::IMAP::SequenceSet.new("1,2,3:7,5,6:10,2048,1024")
|
|
507
|
+
# set.valid_string #=> "1,2,3:7,5,6:10,2048,1024"
|
|
508
|
+
# set = Net::IMAP::SequenceSet.new(1, 2, 3..7, 5, 6..10, 2048, 1024)
|
|
509
|
+
# set.valid_string #=> "1:10,1024,2048"
|
|
510
|
+
#
|
|
511
|
+
# With no arguments (or +nil+) creates an empty sequence set. Note that
|
|
512
|
+
# an empty sequence set is invalid in the \IMAP grammar.
|
|
513
|
+
#
|
|
514
|
+
# set = Net::IMAP::SequenceSet.new
|
|
515
|
+
# set.empty? #=> true
|
|
516
|
+
# set.valid? #=> false
|
|
517
|
+
# set.valid_string #!> raises DataFormatError
|
|
518
|
+
# set << 1..10
|
|
519
|
+
# set.empty? #=> false
|
|
520
|
+
# set.valid? #=> true
|
|
521
|
+
# set.valid_string #=> "1:10"
|
|
522
|
+
#
|
|
523
|
+
# When +input+ is a SequenceSet, ::new behaves the same as calling #dup on
|
|
524
|
+
# that other set. The input's #string will be preserved.
|
|
525
|
+
#
|
|
526
|
+
# input = Net::IMAP::SequenceSet.new("1,2,3:7,5,6:10,2048,1024")
|
|
527
|
+
# copy = Net::IMAP::SequenceSet.new(input)
|
|
528
|
+
# input.valid_string #=> "1,2,3:7,5,6:10,2048,1024"
|
|
529
|
+
# copy.valid_string #=> "1,2,3:7,5,6:10,2048,1024"
|
|
530
|
+
# copy2 = input.dup # same as calling new with a SequenceSet input
|
|
531
|
+
# copy == input #=> true, same set membership
|
|
532
|
+
# copy.eql? input #=> true, same string value
|
|
533
|
+
# copy.equal? input #=> false, different objects
|
|
534
|
+
#
|
|
535
|
+
# copy.normalize!
|
|
536
|
+
# copy.valid_string #=> "1:10,1024,2048"
|
|
537
|
+
# copy == input #=> true, same set membership
|
|
538
|
+
# copy.eql? input #=> false, different string value
|
|
539
|
+
#
|
|
540
|
+
# copy << 999
|
|
541
|
+
# copy.valid_string #=> "1:10,999,1024,2048"
|
|
542
|
+
# copy == input #=> false, different set membership
|
|
543
|
+
# copy.eql? input #=> false, different string value
|
|
544
|
+
#
|
|
545
|
+
# === Alternative set creation methods
|
|
546
|
+
#
|
|
547
|
+
# * ::[] returns a frozen validated (non-empty) SequenceSet, without
|
|
548
|
+
# allocating a new object when the input is already a valid frozen
|
|
549
|
+
# SequenceSet.
|
|
550
|
+
# * Net::IMAP::SequenceSet() coerces an input to SequenceSet, without
|
|
551
|
+
# allocating a new object when the input is already a SequenceSet.
|
|
552
|
+
# * ::try_convert calls +to_sequence_set+ on inputs that support it and
|
|
553
|
+
# returns +nil+ for inputs that don't.
|
|
554
|
+
# * ::empty and ::full both return frozen singleton sets which can be
|
|
555
|
+
# combined with set operations (#|, #&, #^, #-, etc) to make new sets.
|
|
556
|
+
#
|
|
557
|
+
# See SequenceSet@Creating+sequence+sets.
|
|
558
|
+
def initialize(input = nil)
|
|
559
|
+
@set_data = new_set_data
|
|
560
|
+
@string = nil
|
|
561
|
+
replace(input) unless input.nil?
|
|
562
|
+
end
|
|
384
563
|
|
|
385
564
|
# Removes all elements and returns self.
|
|
386
|
-
def clear
|
|
565
|
+
def clear
|
|
566
|
+
modifying! # redundant check (normalizes the error message for JRuby)
|
|
567
|
+
set_data.clear
|
|
568
|
+
@string = nil
|
|
569
|
+
self
|
|
570
|
+
end
|
|
387
571
|
|
|
388
572
|
# Replace the contents of the set with the contents of +other+ and returns
|
|
389
573
|
# +self+.
|
|
390
574
|
#
|
|
391
|
-
# +other+ may be another SequenceSet
|
|
392
|
-
#
|
|
575
|
+
# +other+ may be another SequenceSet or any other object that would be
|
|
576
|
+
# accepted by ::new.
|
|
393
577
|
def replace(other)
|
|
394
578
|
case other
|
|
395
|
-
when SequenceSet then
|
|
579
|
+
when SequenceSet then
|
|
580
|
+
modifying! # short circuit before doing any work
|
|
581
|
+
@set_data = other.dup_set_data
|
|
582
|
+
@string = other.instance_variable_get(:@string)
|
|
396
583
|
when String then self.string = other
|
|
397
584
|
else clear; merge other
|
|
398
585
|
end
|
|
@@ -421,43 +608,51 @@ module Net
|
|
|
421
608
|
# If the set was created from a single string, it is not normalized. If
|
|
422
609
|
# the set is updated the string will be normalized.
|
|
423
610
|
#
|
|
424
|
-
# Related: #valid_string, #normalized_string, #to_s
|
|
425
|
-
def string; @string
|
|
611
|
+
# Related: #valid_string, #normalized_string, #to_s, #inspect
|
|
612
|
+
def string; @string || normalized_string if valid? end
|
|
426
613
|
|
|
427
614
|
# Returns an array with #normalized_string when valid and an empty array
|
|
428
615
|
# otherwise.
|
|
429
616
|
def deconstruct; valid? ? [normalized_string] : [] end
|
|
430
617
|
|
|
431
|
-
# Assigns a new string to #string and resets #elements to match.
|
|
432
|
-
#
|
|
433
|
-
#
|
|
618
|
+
# Assigns a new string to #string and resets #elements to match.
|
|
619
|
+
# Assigning +nil+ or an empty string are equivalent to calling #clear.
|
|
620
|
+
#
|
|
621
|
+
# Non-empty strings are validated but not normalized.
|
|
434
622
|
#
|
|
435
|
-
# Use #add or #
|
|
623
|
+
# Use #add, #merge, or #append to add a string to an existing set.
|
|
436
624
|
#
|
|
437
625
|
# Related: #replace, #clear
|
|
438
|
-
def string=(
|
|
439
|
-
if
|
|
626
|
+
def string=(input)
|
|
627
|
+
if input.nil?
|
|
440
628
|
clear
|
|
629
|
+
elsif (str = String.try_convert(input))
|
|
630
|
+
modifying! # short-circuit before parsing the string
|
|
631
|
+
entries = each_parsed_entry(str).to_a
|
|
632
|
+
clear
|
|
633
|
+
if normalized_entries?(entries)
|
|
634
|
+
replace_minmaxes entries.map!(&:minmax)
|
|
635
|
+
else
|
|
636
|
+
add_minmaxes entries.map!(&:minmax)
|
|
637
|
+
@string = -str
|
|
638
|
+
end
|
|
441
639
|
else
|
|
442
|
-
|
|
443
|
-
tuples = str_to_tuples str
|
|
444
|
-
@tuples, @string = [], -str
|
|
445
|
-
tuples_add tuples
|
|
640
|
+
raise ArgumentError, "expected a string or nil, got #{input.class}"
|
|
446
641
|
end
|
|
642
|
+
input
|
|
447
643
|
end
|
|
448
644
|
|
|
449
645
|
# Returns the \IMAP +sequence-set+ string representation, or an empty
|
|
450
646
|
# string when the set is empty. Note that an empty set is invalid in the
|
|
451
647
|
# \IMAP syntax.
|
|
452
648
|
#
|
|
453
|
-
# Related: #valid_string, #normalized_string, #
|
|
649
|
+
# Related: #string, #valid_string, #normalized_string, #inspect
|
|
454
650
|
def to_s; string || "" end
|
|
455
651
|
|
|
456
652
|
# Freezes and returns the set. A frozen SequenceSet is Ractor-safe.
|
|
457
653
|
def freeze
|
|
458
654
|
return self if frozen?
|
|
459
|
-
|
|
460
|
-
@tuples.each(&:freeze).freeze
|
|
655
|
+
freeze_set_data
|
|
461
656
|
super
|
|
462
657
|
end
|
|
463
658
|
|
|
@@ -479,7 +674,7 @@ module Net
|
|
|
479
674
|
# Related: #eql?, #normalize
|
|
480
675
|
def ==(other)
|
|
481
676
|
self.class == other.class &&
|
|
482
|
-
(to_s == other.to_s ||
|
|
677
|
+
(to_s == other.to_s || set_data == other.set_data)
|
|
483
678
|
end
|
|
484
679
|
|
|
485
680
|
# :call-seq: eql?(other) -> true or false
|
|
@@ -503,8 +698,9 @@ module Net
|
|
|
503
698
|
|
|
504
699
|
# :call-seq: self === other -> true | false | nil
|
|
505
700
|
#
|
|
506
|
-
# Returns whether +other+ is contained within the set.
|
|
507
|
-
#
|
|
701
|
+
# Returns whether +other+ is contained within the set. +other+ may be any
|
|
702
|
+
# object that would be accepted by ::new. Returns +nil+ if StandardError
|
|
703
|
+
# is raised while converting +other+ to a comparable type.
|
|
508
704
|
#
|
|
509
705
|
# Related: #cover?, #include?, #include_star?
|
|
510
706
|
def ===(other)
|
|
@@ -518,12 +714,12 @@ module Net
|
|
|
518
714
|
# Returns whether +other+ is contained within the set. +other+ may be any
|
|
519
715
|
# object that would be accepted by ::new.
|
|
520
716
|
#
|
|
521
|
-
# Related: #===, #include?, #include_star?
|
|
522
|
-
def cover?(other)
|
|
717
|
+
# Related: #===, #include?, #include_star?, #intersect?
|
|
718
|
+
def cover?(other) import_runs(other).none? { !include_run?(_1) } end
|
|
523
719
|
|
|
524
720
|
# Returns +true+ when a given number or range is in +self+, and +false+
|
|
525
|
-
# otherwise. Returns +
|
|
526
|
-
# <tt>*</tt
|
|
721
|
+
# otherwise. Returns +nil+ when +number+ isn't a valid SequenceSet
|
|
722
|
+
# element (Integer, Range, <tt>*</tt>, +sequence-set+ string).
|
|
527
723
|
#
|
|
528
724
|
# set = Net::IMAP::SequenceSet["5:10,100,111:115"]
|
|
529
725
|
# set.include? 1 #=> false
|
|
@@ -531,8 +727,8 @@ module Net
|
|
|
531
727
|
# set.include? 11..20 #=> false
|
|
532
728
|
# set.include? 100 #=> true
|
|
533
729
|
# set.include? 6 #=> true, covered by "5:10"
|
|
534
|
-
# set.include?
|
|
535
|
-
# set.include? "
|
|
730
|
+
# set.include? 6..9 #=> true, covered by "5:10"
|
|
731
|
+
# set.include? "6:9" #=> true, strings are parsed
|
|
536
732
|
# set.include? 4..9 #=> false, intersection is not sufficient
|
|
537
733
|
# set.include? "*" #=> false, use #limit to re-interpret "*"
|
|
538
734
|
# set.include? -1 #=> false, -1 is interpreted as "*"
|
|
@@ -541,16 +737,19 @@ module Net
|
|
|
541
737
|
# set.include? :* #=> true
|
|
542
738
|
# set.include? "*" #=> true
|
|
543
739
|
# set.include? -1 #=> true
|
|
544
|
-
# set.include?
|
|
545
|
-
# set.include?
|
|
740
|
+
# set.include?(200..) #=> true
|
|
741
|
+
# set.include?(100..) #=> false
|
|
546
742
|
#
|
|
547
|
-
# Related: #include_star?, #cover?,
|
|
548
|
-
def include?(element)
|
|
743
|
+
# Related: #include_star?, #cover?, #===, #intersect?
|
|
744
|
+
def include?(element)
|
|
745
|
+
run = import_run element rescue nil
|
|
746
|
+
!!include_run?(run) if run
|
|
747
|
+
end
|
|
549
748
|
|
|
550
749
|
alias member? include?
|
|
551
750
|
|
|
552
751
|
# Returns +true+ when the set contains <tt>*</tt>.
|
|
553
|
-
def include_star?;
|
|
752
|
+
def include_star?; max_num == STAR_INT end
|
|
554
753
|
|
|
555
754
|
# Returns +true+ if the set and a given object have any common elements,
|
|
556
755
|
# +false+ otherwise.
|
|
@@ -558,9 +757,9 @@ module Net
|
|
|
558
757
|
# Net::IMAP::SequenceSet["5:10"].intersect? "7,9,11" #=> true
|
|
559
758
|
# Net::IMAP::SequenceSet["5:10"].intersect? "11:33" #=> false
|
|
560
759
|
#
|
|
561
|
-
# Related: #intersection, #disjoint?
|
|
760
|
+
# Related: #intersection, #disjoint?, #cover?, #include?
|
|
562
761
|
def intersect?(other)
|
|
563
|
-
valid? &&
|
|
762
|
+
valid? && import_runs(other).any? { intersect_run? _1 }
|
|
564
763
|
end
|
|
565
764
|
alias overlap? intersect?
|
|
566
765
|
|
|
@@ -572,12 +771,12 @@ module Net
|
|
|
572
771
|
#
|
|
573
772
|
# Related: #intersection, #intersect?
|
|
574
773
|
def disjoint?(other)
|
|
575
|
-
empty? ||
|
|
774
|
+
empty? || import_runs(other).none? { intersect_run? _1 }
|
|
576
775
|
end
|
|
577
776
|
|
|
578
777
|
# :call-seq:
|
|
579
778
|
# max(star: :*) => integer or star or nil
|
|
580
|
-
# max(count
|
|
779
|
+
# max(count) => SequenceSet
|
|
581
780
|
#
|
|
582
781
|
# Returns the maximum value in +self+, +star+ when the set includes
|
|
583
782
|
# <tt>*</tt>, or +nil+ when the set is empty.
|
|
@@ -589,15 +788,19 @@ module Net
|
|
|
589
788
|
# Related: #min, #minmax, #slice
|
|
590
789
|
def max(count = nil, star: :*)
|
|
591
790
|
if count
|
|
592
|
-
|
|
593
|
-
|
|
791
|
+
if cardinality <= count
|
|
792
|
+
frozen? ? self : dup
|
|
793
|
+
else
|
|
794
|
+
slice(-count..) || remain_frozen_empty
|
|
795
|
+
end
|
|
796
|
+
elsif (val = max_num)
|
|
594
797
|
val == STAR_INT ? star : val
|
|
595
798
|
end
|
|
596
799
|
end
|
|
597
800
|
|
|
598
801
|
# :call-seq:
|
|
599
802
|
# min(star: :*) => integer or star or nil
|
|
600
|
-
# min(count
|
|
803
|
+
# min(count) => SequenceSet
|
|
601
804
|
#
|
|
602
805
|
# Returns the minimum value in +self+, +star+ when the only value in the
|
|
603
806
|
# set is <tt>*</tt>, or +nil+ when the set is empty.
|
|
@@ -610,15 +813,16 @@ module Net
|
|
|
610
813
|
def min(count = nil, star: :*)
|
|
611
814
|
if count
|
|
612
815
|
slice(0...count) || remain_frozen_empty
|
|
613
|
-
elsif (val =
|
|
816
|
+
elsif (val = min_num)
|
|
614
817
|
val != STAR_INT ? val : star
|
|
615
818
|
end
|
|
616
819
|
end
|
|
617
820
|
|
|
618
|
-
# :call-seq: minmax(star: :*) =>
|
|
821
|
+
# :call-seq: minmax(star: :*) => [min, max] or nil
|
|
619
822
|
#
|
|
620
823
|
# Returns a 2-element array containing the minimum and maximum numbers in
|
|
621
|
-
# +self+, or +nil+ when the set is empty.
|
|
824
|
+
# +self+, or +nil+ when the set is empty. +star+ is handled the same way
|
|
825
|
+
# as by #min and #max.
|
|
622
826
|
#
|
|
623
827
|
# Related: #min, #max
|
|
624
828
|
def minmax(star: :*); [min(star: star), max(star: star)] unless empty? end
|
|
@@ -627,10 +831,10 @@ module Net
|
|
|
627
831
|
def valid?; !empty? end
|
|
628
832
|
|
|
629
833
|
# Returns true if the set contains no elements
|
|
630
|
-
def empty?;
|
|
834
|
+
def empty?; runs.empty? end
|
|
631
835
|
|
|
632
836
|
# Returns true if the set contains every possible element.
|
|
633
|
-
def full?;
|
|
837
|
+
def full?; set_data == FULL_SET_DATA end
|
|
634
838
|
|
|
635
839
|
# :call-seq:
|
|
636
840
|
# self + other -> sequence set
|
|
@@ -640,9 +844,7 @@ module Net
|
|
|
640
844
|
# Returns a new sequence set that has every number in the +other+ object
|
|
641
845
|
# added.
|
|
642
846
|
#
|
|
643
|
-
# +other+ may be any object that would be accepted by ::new
|
|
644
|
-
# bit unsigned integer, range, <tt>sequence-set</tt> formatted string,
|
|
645
|
-
# another sequence set, or an enumerable containing any of these.
|
|
847
|
+
# +other+ may be any object that would be accepted by ::new.
|
|
646
848
|
#
|
|
647
849
|
# Net::IMAP::SequenceSet["1:5"] | 2 | [4..6, 99]
|
|
648
850
|
# #=> Net::IMAP::SequenceSet["1:6,99"]
|
|
@@ -666,9 +868,7 @@ module Net
|
|
|
666
868
|
# Returns a new sequence set built by duplicating this set and removing
|
|
667
869
|
# every number that appears in +other+.
|
|
668
870
|
#
|
|
669
|
-
# +other+ may be any object that would be accepted by ::new
|
|
670
|
-
# bit unsigned integer, range, <tt>sequence-set</tt> formatted string,
|
|
671
|
-
# another sequence set, or an enumerable containing any of these.
|
|
871
|
+
# +other+ may be any object that would be accepted by ::new.
|
|
672
872
|
#
|
|
673
873
|
# Net::IMAP::SequenceSet[1..5] - 2 - 4 - 6
|
|
674
874
|
# #=> Net::IMAP::SequenceSet["1,3,5"]
|
|
@@ -678,7 +878,7 @@ module Net
|
|
|
678
878
|
# ==== Set identities
|
|
679
879
|
#
|
|
680
880
|
# <tt>lhs - rhs</tt> is equivalent to:
|
|
681
|
-
# * <tt>~
|
|
881
|
+
# * <tt>~rhs - ~lhs</tt>
|
|
682
882
|
# * <tt>lhs & ~rhs</tt>
|
|
683
883
|
# * <tt>~(~lhs | rhs)</tt>
|
|
684
884
|
# * <tt>lhs & (lhs ^ rhs)</tt>
|
|
@@ -694,9 +894,7 @@ module Net
|
|
|
694
894
|
# Returns a new sequence set containing only the numbers common to this
|
|
695
895
|
# set and +other+.
|
|
696
896
|
#
|
|
697
|
-
# +other+ may be any object that would be accepted by ::new
|
|
698
|
-
# bit unsigned integer, range, <tt>sequence-set</tt> formatted string,
|
|
699
|
-
# another sequence set, or an enumerable containing any of these.
|
|
897
|
+
# +other+ may be any object that would be accepted by ::new.
|
|
700
898
|
#
|
|
701
899
|
# Net::IMAP::SequenceSet[1..5] & [2, 4, 6]
|
|
702
900
|
# #=> Net::IMAP::SequenceSet["2,4"]
|
|
@@ -712,9 +910,7 @@ module Net
|
|
|
712
910
|
# * <tt>lhs - (lhs - rhs)</tt>
|
|
713
911
|
# * <tt>lhs - (lhs ^ rhs)</tt>
|
|
714
912
|
# * <tt>lhs ^ (lhs - rhs)</tt>
|
|
715
|
-
def &(other)
|
|
716
|
-
remain_frozen dup.subtract SequenceSet.new(other).complement!
|
|
717
|
-
end
|
|
913
|
+
def &(other) remain_frozen dup.intersect! other end
|
|
718
914
|
alias intersection :&
|
|
719
915
|
|
|
720
916
|
# :call-seq:
|
|
@@ -724,9 +920,7 @@ module Net
|
|
|
724
920
|
# Returns a new sequence set containing numbers that are exclusive between
|
|
725
921
|
# this set and +other+.
|
|
726
922
|
#
|
|
727
|
-
# +other+ may be any object that would be accepted by ::new
|
|
728
|
-
# bit unsigned integer, range, <tt>sequence-set</tt> formatted string,
|
|
729
|
-
# another sequence set, or an enumerable containing any of these.
|
|
923
|
+
# +other+ may be any object that would be accepted by ::new.
|
|
730
924
|
#
|
|
731
925
|
# Net::IMAP::SequenceSet[1..5] ^ [2, 4, 6]
|
|
732
926
|
# #=> Net::IMAP::SequenceSet["1,3,5:6"]
|
|
@@ -741,7 +935,7 @@ module Net
|
|
|
741
935
|
# * <tt>(lhs | rhs) - (lhs & rhs)</tt>
|
|
742
936
|
# * <tt>(lhs - rhs) | (rhs - lhs)</tt>
|
|
743
937
|
# * <tt>(lhs ^ other) ^ (other ^ rhs)</tt>
|
|
744
|
-
def ^(other) remain_frozen
|
|
938
|
+
def ^(other) remain_frozen dup.xor! other end
|
|
745
939
|
alias xor :^
|
|
746
940
|
|
|
747
941
|
# :call-seq:
|
|
@@ -776,11 +970,12 @@ module Net
|
|
|
776
970
|
# #string will be regenerated. Use #merge to add many elements at once.
|
|
777
971
|
#
|
|
778
972
|
# Use #append to append new elements to #string. See
|
|
779
|
-
#
|
|
973
|
+
# SequenceSet@Ordered+and+Normalized+sets.
|
|
780
974
|
#
|
|
781
975
|
# Related: #add?, #merge, #union, #append
|
|
782
976
|
def add(element)
|
|
783
|
-
|
|
977
|
+
modifying! # short-circuit before import_run
|
|
978
|
+
add_run import_run element
|
|
784
979
|
normalize!
|
|
785
980
|
end
|
|
786
981
|
alias << add
|
|
@@ -790,16 +985,55 @@ module Net
|
|
|
790
985
|
# Unlike #add, #merge, or #union, the new value is appended to #string.
|
|
791
986
|
# This may result in a #string which has duplicates or is out-of-order.
|
|
792
987
|
#
|
|
793
|
-
#
|
|
988
|
+
# set = Net::IMAP::SequenceSet.new
|
|
989
|
+
# set.append(1..2) # => Net::IMAP::SequenceSet("1:2")
|
|
990
|
+
# set.append(5) # => Net::IMAP::SequenceSet("1:2,5")
|
|
991
|
+
# set.append(4) # => Net::IMAP::SequenceSet("1:2,5,4")
|
|
992
|
+
# set.append(3) # => Net::IMAP::SequenceSet("1:2,5,4,3")
|
|
993
|
+
# set.append(2) # => Net::IMAP::SequenceSet("1:2,5,4,3,2")
|
|
994
|
+
#
|
|
995
|
+
# If +entry+ is a string, it will be converted into normal form.
|
|
996
|
+
#
|
|
997
|
+
# set = Net::IMAP::SequenceSet("4:5,1:2")
|
|
998
|
+
# set.append("6:6") # => Net::IMAP::SequenceSet("4:5,1:2,6")
|
|
999
|
+
# set.append("9:8") # => Net::IMAP::SequenceSet("4:5,1:2,6,8:9")
|
|
1000
|
+
#
|
|
1001
|
+
# If +entry+ adjacently follows the last entry, they will coalesced:
|
|
1002
|
+
# set = Net::IMAP::SequenceSet.new("2,1,9:10")
|
|
1003
|
+
# set.append(11..12) # => Net::IMAP::SequenceSet("2,1,9:12")
|
|
1004
|
+
#
|
|
1005
|
+
# Non-normalized sets store the string <em>in addition to</em> an internal
|
|
1006
|
+
# normalized uint32 set representation. This can more than double memory
|
|
1007
|
+
# usage, so large sets should avoid using #append unless preserving order
|
|
1008
|
+
# is required. See SequenceSet@Ordered+and+Normalized+sets.
|
|
794
1009
|
#
|
|
795
1010
|
# Related: #add, #merge, #union
|
|
796
1011
|
def append(entry)
|
|
797
|
-
modifying!
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
string
|
|
801
|
-
|
|
802
|
-
|
|
1012
|
+
modifying! # short-circuit before import_minmax
|
|
1013
|
+
minmax = import_minmax entry
|
|
1014
|
+
adj = minmax.first - 1
|
|
1015
|
+
if @string.nil? && (runs.empty? || max_num <= adj)
|
|
1016
|
+
# append to elements or coalesce with last element
|
|
1017
|
+
add_minmax minmax
|
|
1018
|
+
return self
|
|
1019
|
+
elsif @string.nil?
|
|
1020
|
+
# generate string for out-of-order append
|
|
1021
|
+
head, comma = normalized_string, ","
|
|
1022
|
+
else
|
|
1023
|
+
# @string already exists... maybe coalesce with last entry
|
|
1024
|
+
head, comma, last_entry = @string.rpartition(",")
|
|
1025
|
+
last_min, last_max = import_minmax last_entry
|
|
1026
|
+
if last_max == adj
|
|
1027
|
+
# coalesce with last entry
|
|
1028
|
+
minmax[0] = last_min
|
|
1029
|
+
else
|
|
1030
|
+
# append to existing string
|
|
1031
|
+
head, comma = @string, ","
|
|
1032
|
+
end
|
|
1033
|
+
end
|
|
1034
|
+
entry = export_minmax minmax
|
|
1035
|
+
add_minmax minmax
|
|
1036
|
+
@string = -"#{head}#{comma}#{entry}"
|
|
803
1037
|
self
|
|
804
1038
|
end
|
|
805
1039
|
|
|
@@ -812,6 +1046,7 @@ module Net
|
|
|
812
1046
|
#
|
|
813
1047
|
# Related: #add, #merge, #union, #include?
|
|
814
1048
|
def add?(element)
|
|
1049
|
+
modifying! # short-circuit before include?
|
|
815
1050
|
add element unless include? element
|
|
816
1051
|
end
|
|
817
1052
|
|
|
@@ -824,7 +1059,8 @@ module Net
|
|
|
824
1059
|
#
|
|
825
1060
|
# Related: #delete?, #delete_at, #subtract, #difference
|
|
826
1061
|
def delete(element)
|
|
827
|
-
|
|
1062
|
+
modifying! # short-circuit before import_run
|
|
1063
|
+
subtract_run import_run element
|
|
828
1064
|
normalize!
|
|
829
1065
|
end
|
|
830
1066
|
|
|
@@ -861,15 +1097,17 @@ module Net
|
|
|
861
1097
|
#
|
|
862
1098
|
# Related: #delete, #delete_at, #subtract, #difference, #disjoint?
|
|
863
1099
|
def delete?(element)
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
1100
|
+
modifying! # short-circuit before import_minmax
|
|
1101
|
+
element = input_try_convert(element)
|
|
1102
|
+
minmax = import_minmax element
|
|
1103
|
+
if number_input?(element)
|
|
1104
|
+
return unless include_minmax? minmax
|
|
1105
|
+
subtract_minmax minmax
|
|
868
1106
|
normalize!
|
|
869
|
-
|
|
1107
|
+
export_num minmax.first
|
|
870
1108
|
else
|
|
871
1109
|
copy = dup
|
|
872
|
-
|
|
1110
|
+
subtract_minmax minmax
|
|
873
1111
|
normalize!
|
|
874
1112
|
copy if copy.subtract(self).valid?
|
|
875
1113
|
end
|
|
@@ -901,35 +1139,34 @@ module Net
|
|
|
901
1139
|
#
|
|
902
1140
|
# Related: #slice, #delete_at, #delete, #delete?, #subtract, #difference
|
|
903
1141
|
def slice!(index, length = nil)
|
|
1142
|
+
modifying! # short-circuit before slice
|
|
904
1143
|
deleted = slice(index, length) and subtract deleted
|
|
905
1144
|
deleted
|
|
906
1145
|
end
|
|
907
1146
|
|
|
908
|
-
# Merges all of the elements that appear in any of
|
|
909
|
-
# set, and returns +self+.
|
|
1147
|
+
# In-place set #union. Merges all of the elements that appear in any of
|
|
1148
|
+
# the +sets+ into this set, and returns +self+.
|
|
910
1149
|
#
|
|
911
|
-
# The +sets+ may be any objects that would be accepted by ::new
|
|
912
|
-
# 32 bit unsigned integers, ranges, <tt>sequence-set</tt> formatted
|
|
913
|
-
# strings, other sequence sets, or enumerables containing any of these.
|
|
1150
|
+
# The +sets+ may be any objects that would be accepted by ::new.
|
|
914
1151
|
#
|
|
915
1152
|
# #string will be regenerated after all sets have been merged.
|
|
916
1153
|
#
|
|
917
1154
|
# Related: #add, #add?, #union
|
|
918
1155
|
def merge(*sets)
|
|
919
|
-
|
|
1156
|
+
modifying! # short-circuit before import_runs
|
|
1157
|
+
add_runs import_runs sets
|
|
920
1158
|
normalize!
|
|
921
1159
|
end
|
|
922
1160
|
|
|
923
|
-
# Removes all of the elements that appear in
|
|
924
|
-
# the set, and returns +self+.
|
|
1161
|
+
# In-place set #difference. Removes all of the elements that appear in
|
|
1162
|
+
# any of the given +sets+ from this set, and returns +self+.
|
|
925
1163
|
#
|
|
926
|
-
# The +sets+ may be any objects that would be accepted by ::new
|
|
927
|
-
# 32 bit unsigned integers, ranges, <tt>sequence-set</tt> formatted
|
|
928
|
-
# strings, other sequence sets, or enumerables containing any of these.
|
|
1164
|
+
# The +sets+ may be any objects that would be accepted by ::new.
|
|
929
1165
|
#
|
|
930
1166
|
# Related: #difference
|
|
931
1167
|
def subtract(*sets)
|
|
932
|
-
|
|
1168
|
+
modifying! # short-circuit before import_runs
|
|
1169
|
+
subtract_runs import_runs sets
|
|
933
1170
|
normalize!
|
|
934
1171
|
end
|
|
935
1172
|
|
|
@@ -941,7 +1178,7 @@ module Net
|
|
|
941
1178
|
# This is useful when the given order is significant, for example in a
|
|
942
1179
|
# ESEARCH response to IMAP#sort.
|
|
943
1180
|
#
|
|
944
|
-
# See
|
|
1181
|
+
# See SequenceSet@Ordered+and+Normalized+sets.
|
|
945
1182
|
#
|
|
946
1183
|
# Related: #each_entry, #elements
|
|
947
1184
|
def entries; each_entry.to_a end
|
|
@@ -950,7 +1187,7 @@ module Net
|
|
|
950
1187
|
#
|
|
951
1188
|
# The returned elements are sorted and coalesced, even when the input
|
|
952
1189
|
# #string is not. <tt>*</tt> will sort last. See #normalize,
|
|
953
|
-
#
|
|
1190
|
+
# SequenceSet@Ordered+and+Normalized+sets.
|
|
954
1191
|
#
|
|
955
1192
|
# By itself, <tt>*</tt> translates to <tt>:*</tt>. A range containing
|
|
956
1193
|
# <tt>*</tt> translates to an endless range. Use #limit to translate both
|
|
@@ -967,7 +1204,7 @@ module Net
|
|
|
967
1204
|
#
|
|
968
1205
|
# The returned elements are sorted and coalesced, even when the input
|
|
969
1206
|
# #string is not. <tt>*</tt> will sort last. See #normalize,
|
|
970
|
-
#
|
|
1207
|
+
# SequenceSet@Ordered+and+Normalized+sets.
|
|
971
1208
|
#
|
|
972
1209
|
# <tt>*</tt> translates to an endless range. By itself, <tt>*</tt>
|
|
973
1210
|
# translates to <tt>:*..</tt>. Use #limit to set <tt>*</tt> to a maximum
|
|
@@ -984,7 +1221,7 @@ module Net
|
|
|
984
1221
|
# Returns a sorted array of all of the number values in the sequence set.
|
|
985
1222
|
#
|
|
986
1223
|
# The returned numbers are sorted and de-duplicated, even when the input
|
|
987
|
-
# #string is not. See #normalize,
|
|
1224
|
+
# #string is not. See #normalize, SequenceSet@Ordered+and+Normalized+sets.
|
|
988
1225
|
#
|
|
989
1226
|
# Net::IMAP::SequenceSet["2,5:9,6,12:11"].numbers
|
|
990
1227
|
# #=> [2, 5, 6, 7, 8, 9, 11, 12]
|
|
@@ -1016,56 +1253,34 @@ module Net
|
|
|
1016
1253
|
# no sorting, deduplication, or coalescing. When #string is in its
|
|
1017
1254
|
# normalized form, this will yield the same values as #each_element.
|
|
1018
1255
|
#
|
|
1019
|
-
# See
|
|
1256
|
+
# See SequenceSet@Ordered+and+Normalized+sets.
|
|
1020
1257
|
#
|
|
1021
1258
|
# Related: #entries, #each_element
|
|
1022
1259
|
def each_entry(&block) # :yields: integer or range or :*
|
|
1023
1260
|
return to_enum(__method__) unless block_given?
|
|
1024
|
-
|
|
1261
|
+
each_entry_run do yield export_run_entry _1 end
|
|
1025
1262
|
end
|
|
1026
1263
|
|
|
1027
1264
|
# Yields each number or range (or <tt>:*</tt>) in #elements to the block
|
|
1028
1265
|
# and returns self. Returns an enumerator when called without a block.
|
|
1029
1266
|
#
|
|
1030
1267
|
# The returned numbers are sorted and de-duplicated, even when the input
|
|
1031
|
-
# #string is not. See #normalize,
|
|
1268
|
+
# #string is not. See #normalize, SequenceSet@Ordered+and+Normalized+sets.
|
|
1032
1269
|
#
|
|
1033
1270
|
# Related: #elements, #each_entry
|
|
1034
1271
|
def each_element # :yields: integer or range or :*
|
|
1035
1272
|
return to_enum(__method__) unless block_given?
|
|
1036
|
-
|
|
1273
|
+
runs.each do yield export_run_entry _1 end
|
|
1037
1274
|
self
|
|
1038
1275
|
end
|
|
1039
1276
|
|
|
1040
|
-
private
|
|
1041
|
-
|
|
1042
|
-
def each_entry_tuple(&block)
|
|
1043
|
-
return to_enum(__method__) unless block_given?
|
|
1044
|
-
if @string
|
|
1045
|
-
@string.split(",") do block.call str_to_tuple _1 end
|
|
1046
|
-
else
|
|
1047
|
-
@tuples.each(&block)
|
|
1048
|
-
end
|
|
1049
|
-
self
|
|
1050
|
-
end
|
|
1051
|
-
|
|
1052
|
-
def tuple_to_entry((min, max))
|
|
1053
|
-
if min == STAR_INT then :*
|
|
1054
|
-
elsif max == STAR_INT then min..
|
|
1055
|
-
elsif min == max then min
|
|
1056
|
-
else min..max
|
|
1057
|
-
end
|
|
1058
|
-
end
|
|
1059
|
-
|
|
1060
|
-
public
|
|
1061
|
-
|
|
1062
1277
|
# Yields each range in #ranges to the block and returns self.
|
|
1063
1278
|
# Returns an enumerator when called without a block.
|
|
1064
1279
|
#
|
|
1065
1280
|
# Related: #ranges
|
|
1066
1281
|
def each_range # :yields: range
|
|
1067
1282
|
return to_enum(__method__) unless block_given?
|
|
1068
|
-
|
|
1283
|
+
minmaxes.each do |min, max|
|
|
1069
1284
|
if min == STAR_INT then yield :*..
|
|
1070
1285
|
elsif max == STAR_INT then yield min..
|
|
1071
1286
|
else yield min..max
|
|
@@ -1084,7 +1299,7 @@ module Net
|
|
|
1084
1299
|
def each_number(&block) # :yields: integer
|
|
1085
1300
|
return to_enum(__method__) unless block_given?
|
|
1086
1301
|
raise RangeError, '%s contains "*"' % [self.class] if include_star?
|
|
1087
|
-
|
|
1302
|
+
minmaxes.each do each_number_in_minmax _1, _2, &block end
|
|
1088
1303
|
self
|
|
1089
1304
|
end
|
|
1090
1305
|
|
|
@@ -1098,16 +1313,7 @@ module Net
|
|
|
1098
1313
|
def each_ordered_number(&block)
|
|
1099
1314
|
return to_enum(__method__) unless block_given?
|
|
1100
1315
|
raise RangeError, '%s contains "*"' % [self.class] if include_star?
|
|
1101
|
-
|
|
1102
|
-
end
|
|
1103
|
-
|
|
1104
|
-
private def each_number_in_tuple(min, max, &block)
|
|
1105
|
-
if min == STAR_INT then yield :*
|
|
1106
|
-
elsif min == max then yield min
|
|
1107
|
-
elsif max != STAR_INT then (min..max).each(&block)
|
|
1108
|
-
else
|
|
1109
|
-
raise RangeError, "#{SequenceSet} cannot enumerate range with '*'"
|
|
1110
|
-
end
|
|
1316
|
+
each_entry_minmax do each_number_in_minmax _1, _2, &block end
|
|
1111
1317
|
end
|
|
1112
1318
|
|
|
1113
1319
|
# Returns a Set with all of the #numbers in the sequence set.
|
|
@@ -1119,35 +1325,103 @@ module Net
|
|
|
1119
1325
|
# Related: #elements, #ranges, #numbers
|
|
1120
1326
|
def to_set; Set.new(numbers) end
|
|
1121
1327
|
|
|
1122
|
-
# Returns the
|
|
1328
|
+
# Returns the number of members in the set.
|
|
1329
|
+
#
|
|
1330
|
+
# Unlike #count, <tt>"*"</tt> is considered to be distinct from
|
|
1331
|
+
# <tt>2³² - 1</tt> (the maximum 32-bit unsigned integer value).
|
|
1332
|
+
#
|
|
1333
|
+
# set = Net::IMAP::SequenceSet[1..10]
|
|
1334
|
+
# set.count #=> 10
|
|
1335
|
+
# set.cardinality #=> 10
|
|
1336
|
+
#
|
|
1337
|
+
# set = Net::IMAP::SequenceSet["4294967295,*"]
|
|
1338
|
+
# set.count #=> 1
|
|
1339
|
+
# set.cardinality #=> 2
|
|
1340
|
+
#
|
|
1341
|
+
# set = Net::IMAP::SequenceSet[1..]
|
|
1342
|
+
# set.count #=> 4294967295
|
|
1343
|
+
# set.cardinality #=> 4294967296
|
|
1344
|
+
#
|
|
1345
|
+
# Related: #count, #count_with_duplicates
|
|
1346
|
+
def cardinality = minmaxes.sum(runs.count) { _2 - _1 }
|
|
1347
|
+
|
|
1348
|
+
# Returns the count of distinct #numbers in the set.
|
|
1123
1349
|
#
|
|
1124
|
-
#
|
|
1125
|
-
# unsigned integer value).
|
|
1350
|
+
# Unlike #cardinality, <tt>"*"</tt> is considered to be equal to
|
|
1351
|
+
# <tt>2³² - 1</tt> (the maximum 32-bit unsigned integer value).
|
|
1126
1352
|
#
|
|
1127
|
-
#
|
|
1353
|
+
# set = Net::IMAP::SequenceSet[1..10]
|
|
1354
|
+
# set.count #=> 10
|
|
1355
|
+
# set.cardinality #=> 10
|
|
1356
|
+
#
|
|
1357
|
+
# set = Net::IMAP::SequenceSet["4294967295,*"]
|
|
1358
|
+
# set.count #=> 1
|
|
1359
|
+
# set.cardinality #=> 2
|
|
1360
|
+
#
|
|
1361
|
+
# set = Net::IMAP::SequenceSet[1..]
|
|
1362
|
+
# set.count #=> 4294967295
|
|
1363
|
+
# set.cardinality #=> 4294967296
|
|
1364
|
+
#
|
|
1365
|
+
# Related: #cardinality, #count_with_duplicates
|
|
1128
1366
|
def count
|
|
1129
|
-
|
|
1130
|
-
(include_star? && include?(UINT32_MAX) ? -1 : 0)
|
|
1367
|
+
cardinality + (include_star? && include?(UINT32_MAX) ? -1 : 0)
|
|
1131
1368
|
end
|
|
1132
1369
|
|
|
1133
|
-
alias size count
|
|
1134
|
-
|
|
1135
1370
|
# Returns the count of numbers in the ordered #entries, including any
|
|
1136
1371
|
# repeated numbers.
|
|
1137
1372
|
#
|
|
1138
|
-
#
|
|
1139
|
-
#
|
|
1140
|
-
#
|
|
1141
|
-
#
|
|
1142
|
-
#
|
|
1143
|
-
#
|
|
1373
|
+
# When #string is normalized, this returns the same as #count. Like
|
|
1374
|
+
# #count, <tt>"*"</tt> is considered to be equal to <tt>2³² - 1</tt> (the
|
|
1375
|
+
# maximum 32-bit unsigned integer value).
|
|
1376
|
+
#
|
|
1377
|
+
# In a range, <tt>"*"</tt> is _not_ considered a duplicate:
|
|
1378
|
+
# set = Net::IMAP::SequenceSet["4294967295:*"]
|
|
1379
|
+
# set.count_with_duplicates #=> 1
|
|
1380
|
+
# set.size #=> 2
|
|
1381
|
+
# set.count #=> 1
|
|
1382
|
+
# set.cardinality #=> 2
|
|
1383
|
+
#
|
|
1384
|
+
# In a separate entry, <tt>"*"</tt> _is_ considered a duplicate:
|
|
1385
|
+
# set = Net::IMAP::SequenceSet["4294967295,*"]
|
|
1386
|
+
# set.count_with_duplicates #=> 2
|
|
1387
|
+
# set.size #=> 2
|
|
1388
|
+
# set.count #=> 1
|
|
1389
|
+
# set.cardinality #=> 2
|
|
1390
|
+
#
|
|
1391
|
+
# Related: #count, #cardinality, #size, #count_duplicates,
|
|
1392
|
+
# #has_duplicates?, #entries
|
|
1144
1393
|
def count_with_duplicates
|
|
1145
1394
|
return count unless @string
|
|
1146
|
-
|
|
1395
|
+
each_entry_minmax.sum {|min, max|
|
|
1147
1396
|
max - min + ((max == STAR_INT && min != STAR_INT) ? 0 : 1)
|
|
1148
1397
|
}
|
|
1149
1398
|
end
|
|
1150
1399
|
|
|
1400
|
+
# Returns the combined size of the ordered #entries, including any
|
|
1401
|
+
# repeated numbers.
|
|
1402
|
+
#
|
|
1403
|
+
# When #string is normalized, this returns the same as #cardinality.
|
|
1404
|
+
# Like #cardinality, <tt>"*"</tt> is considered to be be distinct from
|
|
1405
|
+
# <tt>2³² - 1</tt> (the maximum 32-bit unsigned integer value).
|
|
1406
|
+
#
|
|
1407
|
+
# set = Net::IMAP::SequenceSet["4294967295:*"]
|
|
1408
|
+
# set.size #=> 2
|
|
1409
|
+
# set.count_with_duplicates #=> 1
|
|
1410
|
+
# set.count #=> 1
|
|
1411
|
+
# set.cardinality #=> 2
|
|
1412
|
+
#
|
|
1413
|
+
# set = Net::IMAP::SequenceSet["4294967295,*"]
|
|
1414
|
+
# set.size #=> 2
|
|
1415
|
+
# set.count_with_duplicates #=> 2
|
|
1416
|
+
# set.count #=> 1
|
|
1417
|
+
# set.cardinality #=> 2
|
|
1418
|
+
#
|
|
1419
|
+
# Related: #cardinality, #count_with_duplicates, #count, #entries
|
|
1420
|
+
def size
|
|
1421
|
+
return cardinality unless @string
|
|
1422
|
+
each_entry_minmax.sum {|min, max| max - min + 1 }
|
|
1423
|
+
end
|
|
1424
|
+
|
|
1151
1425
|
# Returns the count of repeated numbers in the ordered #entries, the
|
|
1152
1426
|
# difference between #count_with_duplicates and #count.
|
|
1153
1427
|
#
|
|
@@ -1165,7 +1439,7 @@ module Net
|
|
|
1165
1439
|
#
|
|
1166
1440
|
# Always returns +false+ when #string is normalized.
|
|
1167
1441
|
#
|
|
1168
|
-
# Related: #entries, #count_with_duplicates, #count_duplicates
|
|
1442
|
+
# Related: #entries, #count_with_duplicates, #count_duplicates
|
|
1169
1443
|
def has_duplicates?
|
|
1170
1444
|
return false unless @string
|
|
1171
1445
|
count_with_duplicates != count
|
|
@@ -1176,10 +1450,10 @@ module Net
|
|
|
1176
1450
|
#
|
|
1177
1451
|
# Related: #[], #at, #find_ordered_index
|
|
1178
1452
|
def find_index(number)
|
|
1179
|
-
number =
|
|
1180
|
-
|
|
1453
|
+
number = import_num number
|
|
1454
|
+
each_minmax_with_index(minmaxes) do |min, max, idx_min|
|
|
1181
1455
|
number < min and return nil
|
|
1182
|
-
number <= max and return
|
|
1456
|
+
number <= max and return export_num(idx_min + (number - min))
|
|
1183
1457
|
end
|
|
1184
1458
|
nil
|
|
1185
1459
|
end
|
|
@@ -1189,38 +1463,15 @@ module Net
|
|
|
1189
1463
|
#
|
|
1190
1464
|
# Related: #find_index
|
|
1191
1465
|
def find_ordered_index(number)
|
|
1192
|
-
number =
|
|
1193
|
-
|
|
1466
|
+
number = import_num number
|
|
1467
|
+
each_minmax_with_index(each_entry_minmax) do |min, max, idx_min|
|
|
1194
1468
|
if min <= number && number <= max
|
|
1195
|
-
return
|
|
1469
|
+
return export_num(idx_min + (number - min))
|
|
1196
1470
|
end
|
|
1197
1471
|
end
|
|
1198
1472
|
nil
|
|
1199
1473
|
end
|
|
1200
1474
|
|
|
1201
|
-
private
|
|
1202
|
-
|
|
1203
|
-
def each_tuple_with_index(tuples)
|
|
1204
|
-
idx_min = 0
|
|
1205
|
-
tuples.each do |min, max|
|
|
1206
|
-
idx_max = idx_min + (max - min)
|
|
1207
|
-
yield min, max, idx_min, idx_max
|
|
1208
|
-
idx_min = idx_max + 1
|
|
1209
|
-
end
|
|
1210
|
-
idx_min
|
|
1211
|
-
end
|
|
1212
|
-
|
|
1213
|
-
def reverse_each_tuple_with_index(tuples)
|
|
1214
|
-
idx_max = -1
|
|
1215
|
-
tuples.reverse_each do |min, max|
|
|
1216
|
-
yield min, max, (idx_min = idx_max - (max - min)), idx_max
|
|
1217
|
-
idx_max = idx_min - 1
|
|
1218
|
-
end
|
|
1219
|
-
idx_max
|
|
1220
|
-
end
|
|
1221
|
-
|
|
1222
|
-
public
|
|
1223
|
-
|
|
1224
1475
|
# :call-seq: at(index) -> integer or nil
|
|
1225
1476
|
#
|
|
1226
1477
|
# Returns the number at the given +index+ in the sorted set, without
|
|
@@ -1231,7 +1482,7 @@ module Net
|
|
|
1231
1482
|
#
|
|
1232
1483
|
# Related: #[], #slice, #ordered_at
|
|
1233
1484
|
def at(index)
|
|
1234
|
-
|
|
1485
|
+
seek_number_in_minmaxes(minmaxes, index)
|
|
1235
1486
|
end
|
|
1236
1487
|
|
|
1237
1488
|
# :call-seq: ordered_at(index) -> integer or nil
|
|
@@ -1244,21 +1495,7 @@ module Net
|
|
|
1244
1495
|
#
|
|
1245
1496
|
# Related: #[], #slice, #ordered_at
|
|
1246
1497
|
def ordered_at(index)
|
|
1247
|
-
|
|
1248
|
-
end
|
|
1249
|
-
|
|
1250
|
-
private def lookup_number_by_tuple_index(tuples, index)
|
|
1251
|
-
index = Integer(index.to_int)
|
|
1252
|
-
if index.negative?
|
|
1253
|
-
reverse_each_tuple_with_index(tuples) do |min, max, idx_min, idx_max|
|
|
1254
|
-
idx_min <= index and return from_tuple_int(min + (index - idx_min))
|
|
1255
|
-
end
|
|
1256
|
-
else
|
|
1257
|
-
each_tuple_with_index(tuples) do |min, _, idx_min, idx_max|
|
|
1258
|
-
index <= idx_max and return from_tuple_int(min + (index - idx_min))
|
|
1259
|
-
end
|
|
1260
|
-
end
|
|
1261
|
-
nil
|
|
1498
|
+
seek_number_in_minmaxes(each_entry_minmax, index)
|
|
1262
1499
|
end
|
|
1263
1500
|
|
|
1264
1501
|
# :call-seq:
|
|
@@ -1309,37 +1546,6 @@ module Net
|
|
|
1309
1546
|
|
|
1310
1547
|
alias slice :[]
|
|
1311
1548
|
|
|
1312
|
-
private
|
|
1313
|
-
|
|
1314
|
-
def slice_length(start, length)
|
|
1315
|
-
start = Integer(start.to_int)
|
|
1316
|
-
length = Integer(length.to_int)
|
|
1317
|
-
raise ArgumentError, "length must be positive" unless length.positive?
|
|
1318
|
-
last = start + length - 1 unless start.negative? && start.abs <= length
|
|
1319
|
-
slice_range(start..last)
|
|
1320
|
-
end
|
|
1321
|
-
|
|
1322
|
-
def slice_range(range)
|
|
1323
|
-
first = range.begin || 0
|
|
1324
|
-
last = range.end || -1
|
|
1325
|
-
if range.exclude_end?
|
|
1326
|
-
return remain_frozen_empty if last.zero?
|
|
1327
|
-
last -= 1 if range.end && last != STAR_INT
|
|
1328
|
-
end
|
|
1329
|
-
if (first * last).positive? && last < first
|
|
1330
|
-
remain_frozen_empty
|
|
1331
|
-
elsif (min = at(first))
|
|
1332
|
-
max = at(last)
|
|
1333
|
-
max = :* if max.nil?
|
|
1334
|
-
if max == :* then self & (min..)
|
|
1335
|
-
elsif min <= max then self & (min..max)
|
|
1336
|
-
else remain_frozen_empty
|
|
1337
|
-
end
|
|
1338
|
-
end
|
|
1339
|
-
end
|
|
1340
|
-
|
|
1341
|
-
public
|
|
1342
|
-
|
|
1343
1549
|
# Returns a copy of +self+ which only contains the numbers above +num+.
|
|
1344
1550
|
#
|
|
1345
1551
|
# Net::IMAP::SequenceSet["5,10:22,50"].above(10) # to_s => "11:22,50"
|
|
@@ -1411,7 +1617,7 @@ module Net
|
|
|
1411
1617
|
#
|
|
1412
1618
|
# Related: #limit!
|
|
1413
1619
|
def limit(max:)
|
|
1414
|
-
max =
|
|
1620
|
+
max = import_num(max)
|
|
1415
1621
|
if empty? then self.class.empty
|
|
1416
1622
|
elsif !include_star? && max < min then self.class.empty
|
|
1417
1623
|
elsif max(star: STAR_INT) <= max then frozen? ? self : dup.freeze
|
|
@@ -1424,79 +1630,206 @@ module Net
|
|
|
1424
1630
|
#
|
|
1425
1631
|
# Related: #limit
|
|
1426
1632
|
def limit!(max:)
|
|
1633
|
+
modifying! # short-circuit before querying
|
|
1427
1634
|
star = include_star?
|
|
1428
|
-
max =
|
|
1429
|
-
|
|
1430
|
-
|
|
1635
|
+
max = import_num(max)
|
|
1636
|
+
subtract_minmax [max + 1, STAR_INT]
|
|
1637
|
+
add_minmax [max, max ] if star
|
|
1431
1638
|
normalize!
|
|
1432
1639
|
end
|
|
1433
1640
|
|
|
1434
1641
|
# :call-seq: complement! -> self
|
|
1435
1642
|
#
|
|
1436
|
-
#
|
|
1437
|
-
# possible values _except_ for those
|
|
1643
|
+
# In-place set #complement. Replaces the contents of this set with its
|
|
1644
|
+
# own #complement. It will contain all possible values _except_ for those
|
|
1645
|
+
# currently in the set.
|
|
1438
1646
|
#
|
|
1439
1647
|
# Related: #complement
|
|
1440
1648
|
def complement!
|
|
1649
|
+
modifying! # short-circuit before querying
|
|
1441
1650
|
return replace(self.class.full) if empty?
|
|
1442
1651
|
return clear if full?
|
|
1443
|
-
flat =
|
|
1652
|
+
flat = minmaxes.flat_map { [_1 - 1, _2 + 1] }
|
|
1444
1653
|
if flat.first < 1 then flat.shift else flat.unshift 1 end
|
|
1445
1654
|
if STAR_INT < flat.last then flat.pop else flat.push STAR_INT end
|
|
1446
|
-
|
|
1655
|
+
replace_minmaxes flat.each_slice(2).to_a
|
|
1447
1656
|
normalize!
|
|
1448
1657
|
end
|
|
1449
1658
|
|
|
1450
|
-
#
|
|
1659
|
+
# In-place set #intersection. Removes any elements that are missing from
|
|
1660
|
+
# +other+ from this set, keeping only the #intersection, and returns
|
|
1661
|
+
# +self+.
|
|
1662
|
+
#
|
|
1663
|
+
# +other+ can be any object that would be accepted by ::new.
|
|
1664
|
+
#
|
|
1665
|
+
# set = Net::IMAP::SequenceSet.new(1..5)
|
|
1666
|
+
# set.intersect! [2, 4, 6]
|
|
1667
|
+
# set #=> Net::IMAP::SequenceSet("2,4")
|
|
1668
|
+
#
|
|
1669
|
+
# Related: #intersection, #intersect?
|
|
1670
|
+
def intersect!(other)
|
|
1671
|
+
modifying! # short-circuit before processing input
|
|
1672
|
+
subtract SequenceSet.new(other).complement!
|
|
1673
|
+
end
|
|
1674
|
+
|
|
1675
|
+
# In-place set #xor. Adds any numbers in +other+ that are missing from
|
|
1676
|
+
# this set, removes any numbers in +other+ that are already in this set,
|
|
1677
|
+
# and returns +self+.
|
|
1678
|
+
#
|
|
1679
|
+
# +other+ can be any object that would be accepted by ::new.
|
|
1680
|
+
#
|
|
1681
|
+
# set = Net::IMAP::SequenceSet.new(1..5)
|
|
1682
|
+
# set.xor! [2, 4, 6]
|
|
1683
|
+
# set #=> Net::IMAP::SequenceSet["1,3,5:6"]
|
|
1684
|
+
#
|
|
1685
|
+
# Related: #xor, #merge, #subtract
|
|
1686
|
+
def xor!(other)
|
|
1687
|
+
modifying! # short-circuit before processing input
|
|
1688
|
+
other = SequenceSet.new(other)
|
|
1689
|
+
copy = dup
|
|
1690
|
+
merge(other).subtract(other.subtract(copy.complement!))
|
|
1691
|
+
end
|
|
1692
|
+
|
|
1693
|
+
# Returns whether #string is fully normalized: entries have been sorted,
|
|
1694
|
+
# deduplicated, and coalesced, and all entries are in normal form. See
|
|
1695
|
+
# SequenceSet@Ordered+and+Normalized+sets.
|
|
1696
|
+
#
|
|
1697
|
+
# Net::IMAP::SequenceSet["1,3,5"].normalized? #=> true
|
|
1698
|
+
# Net::IMAP::SequenceSet["20:30"].normalized? #=> true
|
|
1699
|
+
#
|
|
1700
|
+
# Net::IMAP::SequenceSet["3,5,1"].normalized? #=> false, not sorted
|
|
1701
|
+
# Net::IMAP::SequenceSet["1,2,3"].normalized? #=> false, not coalesced
|
|
1702
|
+
# Net::IMAP::SequenceSet["1:5,2"].normalized? #=> false, repeated number
|
|
1703
|
+
#
|
|
1704
|
+
# Net::IMAP::SequenceSet["1:1"].normalized? #=> false, number as range
|
|
1705
|
+
# Net::IMAP::SequenceSet["5:1"].normalized? #=> false, backwards range
|
|
1706
|
+
#
|
|
1707
|
+
# Returns +true+ if (and only if) #string is equal to #normalized_string:
|
|
1708
|
+
# seqset = Net::IMAP::SequenceSet["1:3,5"]
|
|
1709
|
+
# seqset.string #=> "1:3,5"
|
|
1710
|
+
# seqset.normalized_string #=> "1:3,5"
|
|
1711
|
+
# seqset.entries #=> [1..3, 5]
|
|
1712
|
+
# seqset.elements #=> [1..3, 5]
|
|
1713
|
+
# seqset.normalized? #=> true
|
|
1714
|
+
#
|
|
1715
|
+
# seqset = Net::IMAP::SequenceSet["3,1,2"]
|
|
1716
|
+
# seqset.string #=> "3,1,2"
|
|
1717
|
+
# seqset.normalized_string #=> "1:3"
|
|
1718
|
+
# seqset.entries #=> [3, 1, 2]
|
|
1719
|
+
# seqset.elements #=> [1..3]
|
|
1720
|
+
# seqset.normalized? #=> false
|
|
1721
|
+
#
|
|
1722
|
+
# Can return +false+ even when #entries and #elements are the same:
|
|
1723
|
+
# seqset = Net::IMAP::SequenceSet["5:1"]
|
|
1724
|
+
# seqset.string #=> "5:1"
|
|
1725
|
+
# seqset.normalized_string #=> "1:5"
|
|
1726
|
+
# seqset.entries #=> [1..5]
|
|
1727
|
+
# seqset.elements #=> [1..5]
|
|
1728
|
+
# seqset.normalized? #=> false
|
|
1729
|
+
#
|
|
1730
|
+
# Note that empty sets are normalized, even though they are not #valid?:
|
|
1731
|
+
# seqset = Net::IMAP::SequenceSet.empty
|
|
1732
|
+
# seqset.normalized? #=> true
|
|
1733
|
+
# seqset.valid? #=> false
|
|
1734
|
+
#
|
|
1735
|
+
# Related: #normalize, #normalize!, #normalized_string
|
|
1736
|
+
def normalized?
|
|
1737
|
+
@string.nil? || normal_string?(@string)
|
|
1738
|
+
end
|
|
1739
|
+
|
|
1740
|
+
# Returns a SequenceSet with a normalized string representation: entries
|
|
1741
|
+
# have been sorted, deduplicated, and coalesced, and all entries
|
|
1742
|
+
# are in normal form. Returns +self+ for frozen normalized sets, and a
|
|
1743
|
+
# normalized duplicate otherwise.
|
|
1451
1744
|
#
|
|
1452
|
-
#
|
|
1453
|
-
# overlapping elements will be merged into a single larger range.
|
|
1454
|
-
# See Net::IMAP@Ordered+and+Normalized+Sets.
|
|
1745
|
+
# See SequenceSet@Ordered+and+Normalized+sets.
|
|
1455
1746
|
#
|
|
1456
1747
|
# Net::IMAP::SequenceSet["1:5,3:7,10:9,10:11"].normalize
|
|
1457
1748
|
# #=> Net::IMAP::SequenceSet["1:7,9:11"]
|
|
1458
1749
|
#
|
|
1459
|
-
# Related: #normalize!, #normalized_string
|
|
1750
|
+
# Related: #normalize!, #normalized_string, #normalized?
|
|
1460
1751
|
def normalize
|
|
1461
|
-
|
|
1462
|
-
return self if frozen? && str == string
|
|
1463
|
-
remain_frozen dup.instance_exec { @string = str&.-@; self }
|
|
1752
|
+
frozen? && normalized? ? self : remain_frozen(dup.normalize!)
|
|
1464
1753
|
end
|
|
1465
1754
|
|
|
1466
1755
|
# Resets #string to be sorted, deduplicated, and coalesced. Returns
|
|
1467
|
-
# +self+. See
|
|
1756
|
+
# +self+. See SequenceSet@Ordered+and+Normalized+sets.
|
|
1468
1757
|
#
|
|
1469
|
-
# Related: #normalize, #normalized_string
|
|
1758
|
+
# Related: #normalize, #normalized_string, #normalized?
|
|
1470
1759
|
def normalize!
|
|
1760
|
+
modifying! # redundant check (normalizes the error message for JRuby)
|
|
1471
1761
|
@string = nil
|
|
1472
1762
|
self
|
|
1473
1763
|
end
|
|
1474
1764
|
|
|
1475
1765
|
# Returns a normalized +sequence-set+ string representation, sorted
|
|
1476
1766
|
# and deduplicated. Adjacent or overlapping elements will be merged into
|
|
1477
|
-
# a single larger range. See
|
|
1767
|
+
# a single larger range. See SequenceSet@Ordered+and+Normalized+sets.
|
|
1478
1768
|
#
|
|
1479
1769
|
# Net::IMAP::SequenceSet["1:5,3:7,10:9,10:11"].normalized_string
|
|
1480
1770
|
# #=> "1:7,9:11"
|
|
1481
1771
|
#
|
|
1482
1772
|
# Returns +nil+ when the set is empty.
|
|
1483
1773
|
#
|
|
1484
|
-
# Related: #normalize!, #normalize
|
|
1774
|
+
# Related: #normalize!, #normalize, #string, #to_s, #normalized?
|
|
1485
1775
|
def normalized_string
|
|
1486
|
-
|
|
1776
|
+
export_runs(runs) unless runs.empty?
|
|
1487
1777
|
end
|
|
1488
1778
|
|
|
1779
|
+
# Returns an inspection string for the SequenceSet.
|
|
1780
|
+
#
|
|
1781
|
+
# Net::IMAP::SequenceSet.new.inspect
|
|
1782
|
+
# #=> "Net::IMAP::SequenceSet()"
|
|
1783
|
+
#
|
|
1784
|
+
# Net::IMAP::SequenceSet(1..5, 1024, 15, 2000).inspect
|
|
1785
|
+
# #=> 'Net::IMAP::SequenceSet("1:5,15,1024,2000")'
|
|
1786
|
+
#
|
|
1787
|
+
# Frozen sets have slightly different output:
|
|
1788
|
+
#
|
|
1789
|
+
# Net::IMAP::SequenceSet.empty.inspect
|
|
1790
|
+
# #=> "Net::IMAP::SequenceSet.empty"
|
|
1791
|
+
#
|
|
1792
|
+
# Net::IMAP::SequenceSet[1..5, 1024, 15, 2000].inspect
|
|
1793
|
+
# #=> 'Net::IMAP::SequenceSet["1:5,15,1024,2000"]'
|
|
1794
|
+
#
|
|
1795
|
+
# Large sets (by number of #entries) have abridged output, with only the
|
|
1796
|
+
# first and last entries:
|
|
1797
|
+
#
|
|
1798
|
+
# Net::IMAP::SequenceSet(((1..5000) % 2).to_a).inspect
|
|
1799
|
+
# #=> #<Net::IMAP::SequenceSet 2500 entries "1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31,...(2468 entries omitted)...,4969,4971,4973,4975,4977,4979,4981,4983,4985,4987,4989,4991,4993,4995,4997,4999">
|
|
1800
|
+
#
|
|
1801
|
+
# Related: #to_s, #string
|
|
1489
1802
|
def inspect
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1803
|
+
case (count = count_entries)
|
|
1804
|
+
when 0
|
|
1805
|
+
(frozen? ? "%s.empty" : "%s()") % [self.class]
|
|
1806
|
+
when ..INSPECT_MAX_LEN
|
|
1807
|
+
(frozen? ? "%s[%p]" : "%s(%p)") % [self.class, to_s]
|
|
1494
1808
|
else
|
|
1495
|
-
|
|
1809
|
+
if @string
|
|
1810
|
+
head = @string[INSPECT_ABRIDGED_HEAD_RE]
|
|
1811
|
+
tail = @string[INSPECT_ABRIDGED_TAIL_RE]
|
|
1812
|
+
else
|
|
1813
|
+
head = export_runs(runs.first(INSPECT_TRUNCATE_LEN)) + ","
|
|
1814
|
+
tail = "," + export_runs(runs.last(INSPECT_TRUNCATE_LEN))
|
|
1815
|
+
end
|
|
1816
|
+
'#<%s %d entries "%s...(%d entries omitted)...%s"%s>' % [
|
|
1817
|
+
self.class, count,
|
|
1818
|
+
head, count - INSPECT_TRUNCATE_LEN * 2, tail,
|
|
1819
|
+
frozen? ? " (frozen)" : "",
|
|
1820
|
+
]
|
|
1496
1821
|
end
|
|
1497
1822
|
end
|
|
1498
1823
|
|
|
1499
|
-
|
|
1824
|
+
##
|
|
1825
|
+
# :method: to_sequence_set
|
|
1826
|
+
# :call-seq: to_sequence_set -> self
|
|
1827
|
+
#
|
|
1828
|
+
# Returns +self+
|
|
1829
|
+
#
|
|
1830
|
+
# Related: ::try_convert
|
|
1831
|
+
|
|
1832
|
+
# :nodoc: (work around rdoc bug)
|
|
1500
1833
|
alias to_sequence_set itself
|
|
1501
1834
|
|
|
1502
1835
|
# Unstable API: currently for internal use only (Net::IMAP#validate_data)
|
|
@@ -1518,13 +1851,17 @@ module Net
|
|
|
1518
1851
|
|
|
1519
1852
|
# For YAML deserialization
|
|
1520
1853
|
def init_with(coder) # :nodoc:
|
|
1521
|
-
@
|
|
1854
|
+
@set_data = new_set_data
|
|
1522
1855
|
self.string = coder['string']
|
|
1523
1856
|
end
|
|
1524
1857
|
|
|
1858
|
+
# :stopdoc:
|
|
1525
1859
|
protected
|
|
1526
1860
|
|
|
1527
|
-
attr_reader :
|
|
1861
|
+
attr_reader :set_data
|
|
1862
|
+
|
|
1863
|
+
alias runs set_data
|
|
1864
|
+
alias minmaxes runs
|
|
1528
1865
|
|
|
1529
1866
|
private
|
|
1530
1867
|
|
|
@@ -1533,39 +1870,42 @@ module Net
|
|
|
1533
1870
|
|
|
1534
1871
|
# frozen clones are shallow copied
|
|
1535
1872
|
def initialize_clone(other)
|
|
1536
|
-
other.
|
|
1873
|
+
@set_data = other.dup_set_data unless other.frozen?
|
|
1874
|
+
super
|
|
1537
1875
|
end
|
|
1538
1876
|
|
|
1539
1877
|
def initialize_dup(other)
|
|
1540
|
-
@
|
|
1541
|
-
@string = other.string&.-@
|
|
1878
|
+
@set_data = other.dup_set_data
|
|
1542
1879
|
super
|
|
1543
1880
|
end
|
|
1544
1881
|
|
|
1545
|
-
|
|
1546
|
-
|
|
1882
|
+
######################################################################{{{2
|
|
1883
|
+
# Import methods
|
|
1884
|
+
|
|
1885
|
+
def import_minmax(input)
|
|
1886
|
+
entry = input_try_convert input
|
|
1547
1887
|
case entry
|
|
1548
|
-
when *STARS, Integer then [int =
|
|
1549
|
-
when Range then
|
|
1550
|
-
when String then
|
|
1888
|
+
when *STARS, Integer then [int = import_num(entry), int]
|
|
1889
|
+
when Range then import_range_minmax(entry)
|
|
1890
|
+
when String then parse_minmax(entry)
|
|
1551
1891
|
else
|
|
1552
|
-
raise DataFormatError, "expected number or range, got %p" % [
|
|
1892
|
+
raise DataFormatError, "expected number or range, got %p" % [input]
|
|
1553
1893
|
end
|
|
1554
1894
|
end
|
|
1895
|
+
alias import_run import_minmax
|
|
1555
1896
|
|
|
1556
|
-
def
|
|
1557
|
-
set = input_try_convert
|
|
1897
|
+
def import_runs(input)
|
|
1898
|
+
set = input_try_convert input
|
|
1558
1899
|
case set
|
|
1559
|
-
when *STARS, Integer, Range then [
|
|
1560
|
-
when String then
|
|
1561
|
-
when SequenceSet then set.
|
|
1562
|
-
when Set then set.map { [
|
|
1563
|
-
when Array then set.flat_map {
|
|
1900
|
+
when *STARS, Integer, Range then [import_run(set)]
|
|
1901
|
+
when String then parse_runs set
|
|
1902
|
+
when SequenceSet then set.runs
|
|
1903
|
+
when Set then set.map { [import_num(_1)] * 2 }
|
|
1904
|
+
when Array then set.flat_map { import_runs _1 }
|
|
1564
1905
|
when nil then []
|
|
1565
1906
|
else
|
|
1566
|
-
raise DataFormatError,
|
|
1567
|
-
|
|
1568
|
-
"got %p" % [set]
|
|
1907
|
+
raise DataFormatError, "expected nz-number, range, '*', Set, Array; " \
|
|
1908
|
+
"got %p" % [input]
|
|
1569
1909
|
end
|
|
1570
1910
|
end
|
|
1571
1911
|
|
|
@@ -1578,9 +1918,17 @@ module Net
|
|
|
1578
1918
|
input
|
|
1579
1919
|
end
|
|
1580
1920
|
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1921
|
+
# NOTE: input_try_convert must be called on input first
|
|
1922
|
+
def number_input?(input)
|
|
1923
|
+
case input
|
|
1924
|
+
when *STARS, Integer then true
|
|
1925
|
+
when String then !input.include?(/[:,]/)
|
|
1926
|
+
end
|
|
1927
|
+
end
|
|
1928
|
+
|
|
1929
|
+
def import_range_minmax(range)
|
|
1930
|
+
first = import_num(range.begin || 1)
|
|
1931
|
+
last = import_num(range.end || :*)
|
|
1584
1932
|
last -= 1 if range.exclude_end? && range.end && last != STAR_INT
|
|
1585
1933
|
unless first <= last
|
|
1586
1934
|
raise DataFormatError, "invalid range for sequence-set: %p" % [range]
|
|
@@ -1588,67 +1936,260 @@ module Net
|
|
|
1588
1936
|
[first, last]
|
|
1589
1937
|
end
|
|
1590
1938
|
|
|
1591
|
-
def
|
|
1592
|
-
def
|
|
1939
|
+
def import_num(obj) STARS.include?(obj) ? STAR_INT : nz_number(obj) end
|
|
1940
|
+
def nz_number(num) = NumValidator.coerce_nz_number(num)
|
|
1941
|
+
|
|
1942
|
+
######################################################################{{{2
|
|
1943
|
+
# Export methods
|
|
1944
|
+
|
|
1945
|
+
def export_num(num) num == STAR_INT ? :* : num end
|
|
1946
|
+
|
|
1947
|
+
def export_minmaxes(minmaxes)
|
|
1948
|
+
-minmaxes.map { export_minmax _1 }.join(",")
|
|
1949
|
+
end
|
|
1950
|
+
|
|
1951
|
+
def export_minmax(minmax) minmax.uniq.map { export_num _1 }.join(":") end
|
|
1952
|
+
|
|
1953
|
+
alias export_runs export_minmaxes
|
|
1954
|
+
alias export_run export_minmax
|
|
1955
|
+
|
|
1956
|
+
def export_minmax_entry((min, max))
|
|
1957
|
+
if min == STAR_INT then :*
|
|
1958
|
+
elsif max == STAR_INT then min..
|
|
1959
|
+
elsif min == max then min
|
|
1960
|
+
else min..max
|
|
1961
|
+
end
|
|
1962
|
+
end
|
|
1963
|
+
alias export_run_entry export_minmax_entry
|
|
1964
|
+
|
|
1965
|
+
def each_number_in_minmax(min, max, &block)
|
|
1966
|
+
if min == STAR_INT then yield :*
|
|
1967
|
+
elsif min == max then yield min
|
|
1968
|
+
elsif max != STAR_INT then (min..max).each(&block)
|
|
1969
|
+
else
|
|
1970
|
+
raise RangeError, "#{SequenceSet} cannot enumerate range with '*'"
|
|
1971
|
+
end
|
|
1972
|
+
end
|
|
1973
|
+
|
|
1974
|
+
######################################################################{{{2
|
|
1975
|
+
# Parse methods
|
|
1976
|
+
|
|
1977
|
+
def parse_runs(str) str.split(",", -1).map! { parse_run _1 } end
|
|
1978
|
+
def parse_minmax(str) parse_entry(str).minmax end
|
|
1979
|
+
alias parse_run parse_minmax
|
|
1593
1980
|
|
|
1594
|
-
def
|
|
1595
|
-
def str_to_tuples(str) str.split(",", -1).map! { str_to_tuple _1 } end
|
|
1596
|
-
def str_to_tuple(str)
|
|
1981
|
+
def parse_entry(str)
|
|
1597
1982
|
raise DataFormatError, "invalid sequence set string" if str.empty?
|
|
1598
|
-
str.split(":", 2).map! {
|
|
1983
|
+
str.split(":", 2).map! { import_num _1 }
|
|
1984
|
+
end
|
|
1985
|
+
|
|
1986
|
+
# yields validated but unsorted [num] or [num, num]
|
|
1987
|
+
def each_parsed_entry(str)
|
|
1988
|
+
return to_enum(__method__, str) unless block_given?
|
|
1989
|
+
str&.split(",", -1) do |entry| yield parse_entry(entry) end
|
|
1599
1990
|
end
|
|
1600
1991
|
|
|
1601
|
-
def
|
|
1992
|
+
def normal_string?(str) normalized_entries? each_parsed_entry str end
|
|
1602
1993
|
|
|
1603
|
-
def
|
|
1604
|
-
|
|
1994
|
+
def normalized_entries?(entries)
|
|
1995
|
+
max = nil
|
|
1996
|
+
entries.each do |first, last|
|
|
1997
|
+
return false if last && last <= first # 1:1 or 2:1
|
|
1998
|
+
return false if max && first <= max + 1 # 2,1 or 1,1 or 1,2
|
|
1999
|
+
max = last || first
|
|
2000
|
+
end
|
|
2001
|
+
true
|
|
2002
|
+
end
|
|
2003
|
+
|
|
2004
|
+
######################################################################{{{2
|
|
2005
|
+
# Ordered entry methods
|
|
2006
|
+
|
|
2007
|
+
def count_entries
|
|
2008
|
+
@string ? @string.count(",") + 1 : runs.count
|
|
2009
|
+
end
|
|
2010
|
+
|
|
2011
|
+
def each_entry_minmax(&block)
|
|
2012
|
+
return to_enum(__method__) unless block_given?
|
|
2013
|
+
if @string
|
|
2014
|
+
@string.split(",") do block.call parse_minmax _1 end
|
|
2015
|
+
else
|
|
2016
|
+
minmaxes.each(&block)
|
|
2017
|
+
end
|
|
2018
|
+
self
|
|
2019
|
+
end
|
|
2020
|
+
alias each_entry_run each_entry_minmax
|
|
2021
|
+
|
|
2022
|
+
######################################################################{{{2
|
|
2023
|
+
# Search methods
|
|
2024
|
+
|
|
2025
|
+
def include_minmax?((min, max)) bsearch_range(min)&.cover?(min..max) end
|
|
2026
|
+
|
|
2027
|
+
def intersect_minmax?((min, max))
|
|
2028
|
+
range = bsearch_range(min) and
|
|
1605
2029
|
range.include?(min) || range.include?(max) || (min..max).cover?(range)
|
|
1606
2030
|
end
|
|
1607
2031
|
|
|
2032
|
+
alias include_run? include_minmax?
|
|
2033
|
+
alias intersect_run? intersect_minmax?
|
|
2034
|
+
|
|
2035
|
+
def bsearch_index(num) = minmaxes.bsearch_index { _2 >= num }
|
|
2036
|
+
def bsearch_minmax(num) = minmaxes.bsearch { _2 >= num }
|
|
2037
|
+
def bsearch_range(num) = (min, max = bsearch_minmax(num)) && (min..max)
|
|
2038
|
+
|
|
2039
|
+
######################################################################{{{2
|
|
2040
|
+
# Number indexing methods
|
|
2041
|
+
|
|
2042
|
+
def seek_number_in_minmaxes(minmaxes, index)
|
|
2043
|
+
index = Integer(index.to_int)
|
|
2044
|
+
if index.negative?
|
|
2045
|
+
reverse_each_minmax_with_index(minmaxes) do |min, max, idx_min, idx_max|
|
|
2046
|
+
idx_min <= index and return export_num(min + (index - idx_min))
|
|
2047
|
+
end
|
|
2048
|
+
else
|
|
2049
|
+
each_minmax_with_index(minmaxes) do |min, _, idx_min, idx_max|
|
|
2050
|
+
index <= idx_max and return export_num(min + (index - idx_min))
|
|
2051
|
+
end
|
|
2052
|
+
end
|
|
2053
|
+
nil
|
|
2054
|
+
end
|
|
2055
|
+
|
|
2056
|
+
def each_minmax_with_index(minmaxes)
|
|
2057
|
+
idx_min = 0
|
|
2058
|
+
minmaxes.each do |min, max|
|
|
2059
|
+
idx_max = idx_min + (max - min)
|
|
2060
|
+
yield min, max, idx_min, idx_max
|
|
2061
|
+
idx_min = idx_max + 1
|
|
2062
|
+
end
|
|
2063
|
+
idx_min
|
|
2064
|
+
end
|
|
2065
|
+
|
|
2066
|
+
def reverse_each_minmax_with_index(minmaxes)
|
|
2067
|
+
idx_max = -1
|
|
2068
|
+
minmaxes.reverse_each do |min, max|
|
|
2069
|
+
yield min, max, (idx_min = idx_max - (max - min)), idx_max
|
|
2070
|
+
idx_max = idx_min - 1
|
|
2071
|
+
end
|
|
2072
|
+
idx_max
|
|
2073
|
+
end
|
|
2074
|
+
|
|
2075
|
+
def slice_length(start, length)
|
|
2076
|
+
start = Integer(start.to_int)
|
|
2077
|
+
length = Integer(length.to_int)
|
|
2078
|
+
raise ArgumentError, "length must be positive" unless length.positive?
|
|
2079
|
+
last = start + length - 1 unless start.negative? && start.abs <= length
|
|
2080
|
+
slice_range(start..last)
|
|
2081
|
+
end
|
|
2082
|
+
|
|
2083
|
+
def slice_range(range)
|
|
2084
|
+
first = range.begin || 0
|
|
2085
|
+
last = range.end || -1
|
|
2086
|
+
if range.exclude_end?
|
|
2087
|
+
return remain_frozen_empty if last.zero?
|
|
2088
|
+
last -= 1 if range.end && last != STAR_INT
|
|
2089
|
+
end
|
|
2090
|
+
if (first * last).positive? && last < first
|
|
2091
|
+
remain_frozen_empty
|
|
2092
|
+
elsif (min = at(first))
|
|
2093
|
+
max = at(last)
|
|
2094
|
+
max = :* if max.nil?
|
|
2095
|
+
if max == :* then self & (min..)
|
|
2096
|
+
elsif min <= max then self & (min..max)
|
|
2097
|
+
else remain_frozen_empty
|
|
2098
|
+
end
|
|
2099
|
+
end
|
|
2100
|
+
end
|
|
2101
|
+
|
|
2102
|
+
######################################################################{{{2
|
|
2103
|
+
# Core set data create/freeze/dup primitives
|
|
2104
|
+
|
|
2105
|
+
def new_set_data = []
|
|
2106
|
+
def freeze_set_data = set_data.each(&:freeze).freeze
|
|
2107
|
+
def dup_set_data = set_data.map { _1.dup }
|
|
2108
|
+
protected :dup_set_data
|
|
2109
|
+
|
|
2110
|
+
######################################################################{{{2
|
|
2111
|
+
# Core set data query/enumeration primitives
|
|
2112
|
+
|
|
2113
|
+
def min_num = minmaxes.first&.first
|
|
2114
|
+
def max_num = minmaxes.last&.last
|
|
2115
|
+
|
|
2116
|
+
def min_at(idx) = minmaxes[idx][0]
|
|
2117
|
+
def max_at(idx) = minmaxes[idx][1]
|
|
2118
|
+
|
|
2119
|
+
######################################################################{{{2
|
|
2120
|
+
# Core set data modification primitives
|
|
2121
|
+
|
|
2122
|
+
def set_min_at(idx, min) = minmaxes[idx][0] = min
|
|
2123
|
+
def set_max_at(idx, max) = minmaxes[idx][1] = max
|
|
2124
|
+
def replace_minmaxes(other) = minmaxes.replace(other)
|
|
2125
|
+
def append_minmax(min, max) = minmaxes << [min, max]
|
|
2126
|
+
def insert_minmax(idx, min, max) = minmaxes.insert idx, [min, max]
|
|
2127
|
+
def delete_run_at(idx) = runs.delete_at(idx)
|
|
2128
|
+
def slice_runs!(...) = runs.slice!(...)
|
|
2129
|
+
def truncate_runs!(idx) = runs.slice!(idx..)
|
|
2130
|
+
|
|
2131
|
+
######################################################################{{{2
|
|
2132
|
+
# Update methods
|
|
2133
|
+
|
|
1608
2134
|
def modifying!
|
|
1609
2135
|
if frozen?
|
|
1610
2136
|
raise FrozenError, "can't modify frozen #{self.class}: %p" % [self]
|
|
1611
2137
|
end
|
|
1612
2138
|
end
|
|
1613
2139
|
|
|
1614
|
-
def
|
|
1615
|
-
|
|
2140
|
+
def add_minmaxes(minmaxes)
|
|
2141
|
+
minmaxes.each do |minmax|
|
|
2142
|
+
add_minmax minmax
|
|
2143
|
+
end
|
|
2144
|
+
self
|
|
2145
|
+
end
|
|
2146
|
+
|
|
2147
|
+
def subtract_minmaxes(minmaxes)
|
|
2148
|
+
minmaxes.each do |minmax|
|
|
2149
|
+
subtract_minmax minmax
|
|
2150
|
+
end
|
|
2151
|
+
self
|
|
2152
|
+
end
|
|
1616
2153
|
|
|
1617
2154
|
#
|
|
1618
|
-
# --|=====| |=====new
|
|
1619
|
-
# ?????????-|=====new
|
|
2155
|
+
# --|=====| |=====new run=======| append
|
|
2156
|
+
# ?????????-|=====new run=======|-|===lower===|-- insert
|
|
1620
2157
|
#
|
|
1621
|
-
# |=====new
|
|
2158
|
+
# |=====new run=======|
|
|
1622
2159
|
# ---------??=======lower=======??--------------- noop
|
|
1623
2160
|
#
|
|
1624
2161
|
# ---------??===lower==|--|==| join remaining
|
|
1625
2162
|
# ---------??===lower==|--|==|----|===upper===|-- join until upper
|
|
1626
2163
|
# ---------??===lower==|--|==|--|=====upper===|-- join to upper
|
|
1627
|
-
def
|
|
2164
|
+
def add_minmax(minmax)
|
|
1628
2165
|
modifying!
|
|
1629
|
-
min, max
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
2166
|
+
min, max = minmax
|
|
2167
|
+
lower_idx = bsearch_index(min - 1)
|
|
2168
|
+
lmin, lmax = min_at(lower_idx), max_at(lower_idx) if lower_idx
|
|
2169
|
+
if lmin.nil? then append_minmax min, max
|
|
2170
|
+
elsif (max + 1) < lmin then insert_minmax lower_idx, min, max
|
|
2171
|
+
else add_coalesced_minmax(lower_idx, lmin, lmax, min, max)
|
|
1634
2172
|
end
|
|
1635
2173
|
end
|
|
1636
2174
|
|
|
1637
|
-
def
|
|
1638
|
-
return if
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
lower_idx
|
|
1642
|
-
return if
|
|
1643
|
-
tmax_adj =
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
2175
|
+
def add_coalesced_minmax(lower_idx, lmin, lmax, min, max)
|
|
2176
|
+
return if lmin <= min && max <= lmax
|
|
2177
|
+
set_min_at lower_idx, (lmin = min) if min < lmin
|
|
2178
|
+
set_max_at lower_idx, (lmax = max) if lmax < max
|
|
2179
|
+
next_idx = lower_idx + 1
|
|
2180
|
+
return if next_idx == runs.count
|
|
2181
|
+
tmax_adj = lmax + 1
|
|
2182
|
+
if (upper_idx = bsearch_index(tmax_adj))
|
|
2183
|
+
if tmax_adj < min_at(upper_idx)
|
|
2184
|
+
upper_idx -= 1
|
|
2185
|
+
else
|
|
2186
|
+
set_max_at lower_idx, max_at(upper_idx)
|
|
2187
|
+
end
|
|
1647
2188
|
end
|
|
1648
|
-
|
|
2189
|
+
slice_runs! next_idx..upper_idx
|
|
1649
2190
|
end
|
|
1650
2191
|
|
|
1651
|
-
# |====
|
|
2192
|
+
# |====subtracted run=======|
|
|
1652
2193
|
# --|====| no more 1. noop
|
|
1653
2194
|
# --|====|---------------------------|====lower====|-- 2. noop
|
|
1654
2195
|
# -------|======lower================|---------------- 3. split
|
|
@@ -1661,61 +2202,59 @@ module Net
|
|
|
1661
2202
|
# -------??=====lower====|--|====| no more 6. delete rest
|
|
1662
2203
|
# -------??=====lower====|--|====|---|====upper====|-- 7. delete until
|
|
1663
2204
|
# -------??=====lower====|--|====|--|=====upper====|-- 8. delete and trim
|
|
1664
|
-
def
|
|
2205
|
+
def subtract_minmax(minmax)
|
|
1665
2206
|
modifying!
|
|
1666
|
-
min, max
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
elsif max <
|
|
1671
|
-
|
|
2207
|
+
min, max = minmax
|
|
2208
|
+
idx = bsearch_index(min)
|
|
2209
|
+
lmin, lmax = min_at(idx), max_at(idx) if idx
|
|
2210
|
+
if lmin.nil? then nil # case 1.
|
|
2211
|
+
elsif max < lmin then nil # case 2.
|
|
2212
|
+
elsif max < lmax then trim_or_split_minmax idx, lmin, min, max
|
|
2213
|
+
else trim_or_delete_minmax idx, lmin, lmax, min, max
|
|
1672
2214
|
end
|
|
1673
2215
|
end
|
|
1674
2216
|
|
|
1675
|
-
def
|
|
1676
|
-
|
|
1677
|
-
|
|
2217
|
+
def trim_or_split_minmax(idx, lmin, tmin, tmax)
|
|
2218
|
+
set_min_at idx, tmax + 1
|
|
2219
|
+
if lmin < tmin # split
|
|
2220
|
+
insert_minmax idx, lmin, tmin - 1
|
|
1678
2221
|
end
|
|
1679
|
-
lower[0] = tmax + 1
|
|
1680
2222
|
end
|
|
1681
2223
|
|
|
1682
|
-
def
|
|
1683
|
-
if
|
|
1684
|
-
|
|
2224
|
+
def trim_or_delete_minmax(lower_idx, lmin, lmax, tmin, tmax)
|
|
2225
|
+
if lmin < tmin # trim lower
|
|
2226
|
+
lmax = set_max_at lower_idx, tmin - 1
|
|
1685
2227
|
lower_idx += 1
|
|
1686
2228
|
end
|
|
1687
|
-
if tmax ==
|
|
1688
|
-
|
|
1689
|
-
elsif (
|
|
1690
|
-
upper_idx
|
|
1691
|
-
|
|
2229
|
+
if tmax == lmax # case 5
|
|
2230
|
+
delete_run_at lower_idx
|
|
2231
|
+
elsif (upper_idx = bsearch_index(tmax + 1))
|
|
2232
|
+
if min_at(upper_idx) <= tmax # case 8
|
|
2233
|
+
set_min_at upper_idx, tmax + 1
|
|
2234
|
+
end
|
|
2235
|
+
slice_runs! lower_idx..upper_idx - 1 # cases 7 and 8
|
|
2236
|
+
else # case 6
|
|
2237
|
+
truncate_runs! lower_idx
|
|
1692
2238
|
end
|
|
1693
|
-
tuples.slice!(lower_idx..upper_idx)
|
|
1694
|
-
end
|
|
1695
|
-
|
|
1696
|
-
def tuple_gte_with_index(num)
|
|
1697
|
-
idx = tuples.bsearch_index { _2 >= num } and [tuples[idx], idx]
|
|
1698
2239
|
end
|
|
1699
2240
|
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
def nz_number(num)
|
|
1706
|
-
String === num && !/\A[1-9]\d*\z/.match?(num) and
|
|
1707
|
-
raise DataFormatError, "%p is not a valid nz-number" % [num]
|
|
1708
|
-
NumValidator.ensure_nz_number Integer num
|
|
1709
|
-
rescue TypeError # To catch errors from Integer()
|
|
1710
|
-
raise DataFormatError, $!.message
|
|
1711
|
-
end
|
|
2241
|
+
alias add_runs add_minmaxes
|
|
2242
|
+
alias add_run add_minmax
|
|
2243
|
+
alias subtract_runs subtract_minmaxes
|
|
2244
|
+
alias subtract_run subtract_minmax
|
|
1712
2245
|
|
|
2246
|
+
######################################################################{{{2
|
|
1713
2247
|
# intentionally defined after the class implementation
|
|
1714
2248
|
|
|
2249
|
+
FULL_SET_DATA = [[1, STAR_INT].freeze].freeze
|
|
2250
|
+
private_constant :FULL_SET_DATA
|
|
2251
|
+
|
|
1715
2252
|
EMPTY = new.freeze
|
|
1716
2253
|
FULL = self["1:*"]
|
|
1717
2254
|
private_constant :EMPTY, :FULL
|
|
1718
2255
|
|
|
2256
|
+
# }}}
|
|
2257
|
+
# vim:foldmethod=marker
|
|
1719
2258
|
end
|
|
1720
2259
|
end
|
|
1721
2260
|
end
|