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