net-imap 0.3.9 → 0.5.8

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