net-imap 0.4.10 → 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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/BSDL +22 -0
  3. data/COPYING +56 -0
  4. data/Gemfile +8 -1
  5. data/LICENSE.txt +3 -22
  6. data/README.md +10 -4
  7. data/docs/styles.css +75 -14
  8. data/lib/net/imap/authenticators.rb +2 -2
  9. data/lib/net/imap/command_data.rb +61 -48
  10. data/lib/net/imap/config/attr_accessors.rb +75 -0
  11. data/lib/net/imap/config/attr_inheritance.rb +90 -0
  12. data/lib/net/imap/config/attr_type_coercion.rb +62 -0
  13. data/lib/net/imap/config.rb +552 -0
  14. data/lib/net/imap/connection_state.rb +48 -0
  15. data/lib/net/imap/data_encoding.rb +4 -4
  16. data/lib/net/imap/data_lite.rb +226 -0
  17. data/lib/net/imap/deprecated_client_options.rb +9 -6
  18. data/lib/net/imap/errors.rb +40 -1
  19. data/lib/net/imap/esearch_result.rb +180 -0
  20. data/lib/net/imap/fetch_data.rb +126 -47
  21. data/lib/net/imap/flags.rb +1 -1
  22. data/lib/net/imap/response_data.rb +126 -239
  23. data/lib/net/imap/response_parser/parser_utils.rb +11 -6
  24. data/lib/net/imap/response_parser.rb +188 -34
  25. data/lib/net/imap/response_reader.rb +73 -0
  26. data/lib/net/imap/sasl/anonymous_authenticator.rb +3 -3
  27. data/lib/net/imap/sasl/authentication_exchange.rb +52 -20
  28. data/lib/net/imap/sasl/authenticators.rb +8 -4
  29. data/lib/net/imap/sasl/client_adapter.rb +77 -26
  30. data/lib/net/imap/sasl/cram_md5_authenticator.rb +4 -4
  31. data/lib/net/imap/sasl/digest_md5_authenticator.rb +218 -56
  32. data/lib/net/imap/sasl/external_authenticator.rb +3 -3
  33. data/lib/net/imap/sasl/gs2_header.rb +7 -7
  34. data/lib/net/imap/sasl/login_authenticator.rb +4 -3
  35. data/lib/net/imap/sasl/oauthbearer_authenticator.rb +6 -6
  36. data/lib/net/imap/sasl/plain_authenticator.rb +7 -7
  37. data/lib/net/imap/sasl/protocol_adapters.rb +60 -4
  38. data/lib/net/imap/sasl/scram_authenticator.rb +8 -8
  39. data/lib/net/imap/sasl.rb +8 -5
  40. data/lib/net/imap/sasl_adapter.rb +0 -1
  41. data/lib/net/imap/search_result.rb +2 -2
  42. data/lib/net/imap/sequence_set.rb +471 -176
  43. data/lib/net/imap/stringprep/nameprep.rb +1 -1
  44. data/lib/net/imap/stringprep/trace.rb +4 -4
  45. data/lib/net/imap/uidplus_data.rb +244 -0
  46. data/lib/net/imap/vanished_data.rb +56 -0
  47. data/lib/net/imap.rb +1225 -364
  48. data/net-imap.gemspec +4 -4
  49. data/rakelib/benchmarks.rake +1 -1
  50. data/rakelib/rfcs.rake +2 -0
  51. data/rakelib/string_prep_tables_generator.rb +2 -0
  52. metadata +17 -12
  53. data/.github/dependabot.yml +0 -6
  54. data/.github/workflows/pages.yml +0 -46
  55. data/.github/workflows/test.yml +0 -31
  56. data/.gitignore +0 -12
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "set" unless defined?(::Set)
4
+
3
5
  module Net
4
6
  class IMAP
5
7
 
@@ -14,13 +16,6 @@ module Net
14
16
  # receive a SequenceSet as an argument, for example IMAP#search, IMAP#fetch,
15
17
  # and IMAP#store.
16
18
  #
17
- # == EXPERIMENTAL API
18
- #
19
- # SequenceSet is currently experimental. Only two methods, ::[] and
20
- # #valid_string, are considered stable. Although the API isn't expected to
21
- # change much, any other methods may be removed or changed without
22
- # deprecation.
23
- #
24
19
  # == Creating sequence sets
25
20
  #
26
21
  # SequenceSet.new with no arguments creates an empty sequence set. Note
@@ -37,7 +32,8 @@ module Net
37
32
  #
38
33
  # SequenceSet.new may receive a single optional argument: a non-zero 32 bit
39
34
  # unsigned integer, a range, a <tt>sequence-set</tt> formatted string,
40
- # another sequence set, or an enumerable containing any of these.
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).
41
37
  #
42
38
  # set = Net::IMAP::SequenceSet.new(1)
43
39
  # set.valid_string #=> "1"
@@ -60,18 +56,20 @@ module Net
60
56
  # set = Net::IMAP::SequenceSet[1, 2, [3..7, 5], 6..10, 2048, 1024]
61
57
  # set.valid_string #=> "1:10,55,1024:2048"
62
58
  #
63
- # == Normalized form
59
+ # == Ordered and Normalized sets
64
60
  #
65
- # When a sequence set is created with a single String value, that #string
66
- # representation is preserved. SequenceSet's internal representation
67
- # implicitly sorts all entries, de-duplicates numbers, and coalesces
68
- # adjacent or overlapping ranges. Most enumeration methods and offset-based
69
- # methods use this normalized representation. Most modification methods
70
- # will convert #string to its normalized form.
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.
71
65
  #
72
- # In some cases the order of the string representation is significant, such
73
- # as the +ESORT+, <tt>CONTEXT=SORT</tt>, and +UIDPLUS+ extensions. Use
74
- # #entries or #each_entry to enumerate the set in its original order. To
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
75
73
  # preserve #string order while modifying a set, use #append, #string=, or
76
74
  # #replace.
77
75
  #
@@ -110,11 +108,15 @@ module Net
110
108
  # When a set includes <tt>*</tt>, some methods may have surprising behavior.
111
109
  #
112
110
  # For example, #complement treats <tt>*</tt> as its own number. This way,
113
- # the #intersection of a set and its #complement will always be empty.
114
- # This is not how an \IMAP server interprets the set: it will convert
115
- # <tt>*</tt> to either the number of messages in the mailbox or +UIDNEXT+,
116
- # as appropriate. And there _will_ be overlap between a set and its
117
- # complement after #limit is applied to each:
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:
118
120
  #
119
121
  # ~Net::IMAP::SequenceSet["*"] == Net::IMAP::SequenceSet[1..(2**32-1)]
120
122
  # ~Net::IMAP::SequenceSet[1..5] == Net::IMAP::SequenceSet["6:*"]
@@ -164,7 +166,7 @@ module Net
164
166
  # - #===:
165
167
  # Returns whether a given object is fully contained within +self+, or
166
168
  # +nil+ if the object cannot be converted to a compatible type.
167
- # - #cover? (aliased as #===):
169
+ # - #cover?:
168
170
  # Returns whether a given object is fully contained within +self+.
169
171
  # - #intersect? (aliased as #overlap?):
170
172
  # Returns whether +self+ and a given object have any common elements.
@@ -176,39 +178,50 @@ module Net
176
178
  #
177
179
  # <i>Set membership:</i>
178
180
  # - #include? (aliased as #member?):
179
- # Returns whether a given object (nz-number, range, or <tt>*</tt>) is
181
+ # Returns whether a given element (nz-number, range, or <tt>*</tt>) is
180
182
  # contained by the set.
181
183
  # - #include_star?: Returns whether the set contains <tt>*</tt>.
182
184
  #
183
185
  # <i>Minimum and maximum value elements:</i>
184
- # - #min: Returns the minimum number in the set.
185
- # - #max: Returns the maximum number in the set.
186
- # - #minmax: Returns the minimum and maximum numbers in the set.
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.
187
189
  #
188
- # <i>Accessing value by offset:</i>
190
+ # <i>Accessing value by offset in sorted set:</i>
189
191
  # - #[] (aliased as #slice): Returns the number or consecutive subset at a
190
- # given offset or range of offsets.
191
- # - #at: Returns the number at a given offset.
192
- # - #find_index: Returns the given number's offset in the set
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.
193
200
  #
194
201
  # <i>Set cardinality:</i>
195
202
  # - #count (aliased as #size): Returns the count of numbers in the set.
203
+ # Duplicated numbers are not counted.
196
204
  # - #empty?: Returns whether the set has no members. \IMAP syntax does not
197
205
  # allow empty sequence sets.
198
206
  # - #valid?: Returns whether the set has any members.
199
207
  # - #full?: Returns whether the set contains every possible value, including
200
208
  # <tt>*</tt>.
201
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
+ #
202
218
  # === Methods for Iterating
203
219
  #
220
+ # <i>Normalized (sorted and coalesced):</i>
204
221
  # - #each_element: Yields each number and range in the set, sorted and
205
222
  # coalesced, and returns +self+.
206
223
  # - #elements (aliased as #to_a): Returns an Array of every number and range
207
224
  # in the set, sorted and coalesced.
208
- # - #each_entry: Yields each number and range in the set, unsorted and
209
- # without deduplicating numbers or coalescing ranges, and returns +self+.
210
- # - #entries: Returns an Array of every number and range in the set,
211
- # unsorted and without deduplicating numbers or coalescing ranges.
212
225
  # - #each_range:
213
226
  # Yields each element in the set as a Range and returns +self+.
214
227
  # - #ranges: Returns an Array of every element in the set, converting
@@ -218,47 +231,70 @@ module Net
218
231
  # ranges into all of their contained numbers.
219
232
  # - #to_set: Returns a Set containing all of the #numbers in the set.
220
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
+ #
221
242
  # === Methods for \Set Operations
222
243
  # These methods do not modify +self+.
223
244
  #
224
245
  # - #| (aliased as #union and #+): Returns a new set combining all members
225
- # from +self+ with all members from the other object.
246
+ # from +self+ with all members from the other set.
226
247
  # - #& (aliased as #intersection): Returns a new set containing all members
227
- # common to +self+ and the other object.
248
+ # common to +self+ and the other set.
228
249
  # - #- (aliased as #difference): Returns a copy of +self+ with all members
229
- # in the other object removed.
250
+ # in the other set removed.
230
251
  # - #^ (aliased as #xor): Returns a new set containing all members from
231
- # +self+ and the other object except those common to both.
252
+ # +self+ and the other set except those common to both.
232
253
  # - #~ (aliased as #complement): Returns a new set containing all members
233
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.
234
259
  # - #limit: Returns a copy of +self+ which has replaced <tt>*</tt> with a
235
260
  # given maximum value and removed all members over that maximum.
236
261
  #
237
262
  # === Methods for Assigning
238
263
  # These methods add or replace elements in +self+.
239
264
  #
240
- # - #add (aliased as #<<): Adds a given object to the set; returns +self+.
241
- # - #add?: If the given object is not an element in the set, adds it and
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
242
271
  # returns +self+; otherwise, returns +nil+.
243
- # - #merge: Merges multiple elements into the set; returns +self+.
244
- # - #append: Adds a given object to the set, appending it to the existing
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
245
280
  # string, and returns +self+.
246
281
  # - #string=: Assigns a new #string value and replaces #elements to match.
247
282
  # - #replace: Replaces the contents of the set with the contents
248
283
  # of a given object.
249
- # - #complement!: Replaces the contents of the set with its own #complement.
250
284
  #
251
285
  # === Methods for Deleting
252
- # These methods remove elements from +self+.
286
+ # These methods remove elements from +self+, and update #string to be fully
287
+ # sorted and coalesced.
253
288
  #
254
289
  # - #clear: Removes all elements in the set; returns +self+.
255
- # - #delete: Removes a given object from the set; returns +self+.
256
- # - #delete?: If the given object is an element in the set, removes it and
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
257
292
  # returns it; otherwise, returns +nil+.
258
293
  # - #delete_at: Removes the number at a given offset.
259
294
  # - #slice!: Removes the number or consecutive numbers at a given offset or
260
295
  # range of offsets.
261
- # - #subtract: Removes each given object from the set; returns +self+.
296
+ # - #subtract: Removes all members of the given sets from this set; returns
297
+ # +self+.
262
298
  # - #limit!: Replaces <tt>*</tt> with a given maximum value and removes all
263
299
  # members over that maximum; returns +self+.
264
300
  #
@@ -286,25 +322,24 @@ module Net
286
322
 
287
323
  # valid inputs for "*"
288
324
  STARS = [:*, ?*, -1].freeze
289
- private_constant :STAR_INT, :STARS
290
-
291
- COERCIBLE = ->{ _1.respond_to? :to_sequence_set }
292
- ENUMABLE = ->{ _1.respond_to?(:each) && _1.respond_to?(:empty?) }
293
- private_constant :COERCIBLE, :ENUMABLE
325
+ private_constant :STARS
294
326
 
295
327
  class << self
296
328
 
297
329
  # :call-seq:
298
- # SequenceSet[*values] -> valid frozen sequence set
330
+ # SequenceSet[*inputs] -> valid frozen sequence set
299
331
  #
300
- # Returns a frozen SequenceSet, constructed from +values+.
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.
301
336
  #
302
337
  # An empty SequenceSet is invalid and will raise a DataFormatError.
303
338
  #
304
339
  # Use ::new to create a mutable or empty SequenceSet.
305
340
  def [](first, *rest)
306
341
  if rest.empty?
307
- if first.is_a?(SequenceSet) && set.frozen? && set.valid?
342
+ if first.is_a?(SequenceSet) && first.frozen? && first.valid?
308
343
  first
309
344
  else
310
345
  new(first).validate.freeze
@@ -325,7 +360,7 @@ module Net
325
360
  # raised.
326
361
  def try_convert(obj)
327
362
  return obj if obj.is_a?(SequenceSet)
328
- return nil unless respond_to?(:to_sequence_set)
363
+ return nil unless obj.respond_to?(:to_sequence_set)
329
364
  obj = obj.to_sequence_set
330
365
  return obj if obj.is_a?(SequenceSet)
331
366
  raise DataFormatError, "invalid object returned from to_sequence_set"
@@ -389,6 +424,10 @@ module Net
389
424
  # Related: #valid_string, #normalized_string, #to_s
390
425
  def string; @string ||= normalized_string if valid? end
391
426
 
427
+ # Returns an array with #normalized_string when valid and an empty array
428
+ # otherwise.
429
+ def deconstruct; valid? ? [normalized_string] : [] end
430
+
392
431
  # Assigns a new string to #string and resets #elements to match. It
393
432
  # cannot be set to an empty string—assign +nil+ or use #clear instead.
394
433
  # The string is validated but not normalized.
@@ -536,26 +575,52 @@ module Net
536
575
  empty? || input_to_tuples(other).none? { intersect_tuple? _1 }
537
576
  end
538
577
 
539
- # :call-seq: max(star: :*) => integer or star or nil
578
+ # :call-seq:
579
+ # max(star: :*) => integer or star or nil
580
+ # max(count, star: :*) => SequenceSet
540
581
  #
541
582
  # Returns the maximum value in +self+, +star+ when the set includes
542
583
  # <tt>*</tt>, or +nil+ when the set is empty.
543
- def max(star: :*)
544
- (val = @tuples.last&.last) && val == STAR_INT ? star : val
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
545
596
  end
546
597
 
547
- # :call-seq: min(star: :*) => integer or star or nil
598
+ # :call-seq:
599
+ # min(star: :*) => integer or star or nil
600
+ # min(count, star: :*) => SequenceSet
548
601
  #
549
602
  # Returns the minimum value in +self+, +star+ when the only value in the
550
603
  # set is <tt>*</tt>, or +nil+ when the set is empty.
551
- def min(star: :*)
552
- (val = @tuples.first&.first) && val == STAR_INT ? star : val
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
553
616
  end
554
617
 
555
618
  # :call-seq: minmax(star: :*) => nil or [integer, integer or star]
556
619
  #
557
620
  # Returns a 2-element array containing the minimum and maximum numbers in
558
621
  # +self+, or +nil+ when the set is empty.
622
+ #
623
+ # Related: #min, #max
559
624
  def minmax(star: :*); [min(star: star), max(star: star)] unless empty? end
560
625
 
561
626
  # Returns false when the set is empty.
@@ -582,7 +647,14 @@ module Net
582
647
  # Net::IMAP::SequenceSet["1:5"] | 2 | [4..6, 99]
583
648
  # #=> Net::IMAP::SequenceSet["1:6,99"]
584
649
  #
585
- # Related: #add, #merge
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>
586
658
  def |(other) remain_frozen dup.merge other end
587
659
  alias :+ :|
588
660
  alias union :|
@@ -601,7 +673,17 @@ module Net
601
673
  # Net::IMAP::SequenceSet[1..5] - 2 - 4 - 6
602
674
  # #=> Net::IMAP::SequenceSet["1,3,5"]
603
675
  #
604
- # Related: #subtract
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>
605
687
  def -(other) remain_frozen dup.subtract other end
606
688
  alias difference :-
607
689
 
@@ -619,7 +701,17 @@ module Net
619
701
  # Net::IMAP::SequenceSet[1..5] & [2, 4, 6]
620
702
  # #=> Net::IMAP::SequenceSet["2,4"]
621
703
  #
622
- # <tt>(seqset & other)</tt> is equivalent to <tt>(seqset - ~other)</tt>.
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>
623
715
  def &(other)
624
716
  remain_frozen dup.subtract SequenceSet.new(other).complement!
625
717
  end
@@ -639,9 +731,17 @@ module Net
639
731
  # Net::IMAP::SequenceSet[1..5] ^ [2, 4, 6]
640
732
  # #=> Net::IMAP::SequenceSet["1,3,5:6"]
641
733
  #
642
- # <tt>(seqset ^ other)</tt> is equivalent to <tt>((seqset | other) -
643
- # (seqset & other))</tt>.
644
- def ^(other) remain_frozen (self | other).subtract(self & other) end
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
645
745
  alias xor :^
646
746
 
647
747
  # :call-seq:
@@ -658,21 +758,29 @@ module Net
658
758
  # ~Net::IMAP::SequenceSet["6:99,223:*"]
659
759
  # #=> Net::IMAP::SequenceSet["1:5,100:222"]
660
760
  #
661
- # Related: #complement!
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
662
767
  def ~; remain_frozen dup.complement! end
663
768
  alias complement :~
664
769
 
665
770
  # :call-seq:
666
- # add(object) -> self
771
+ # add(element) -> self
667
772
  # self << other -> self
668
773
  #
669
774
  # Adds a range or number to the set and returns +self+.
670
775
  #
671
776
  # #string will be regenerated. Use #merge to add many elements at once.
672
777
  #
673
- # Related: #add?, #merge, #union
674
- def add(object)
675
- tuple_add input_to_tuple object
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
676
784
  normalize!
677
785
  end
678
786
  alias << add
@@ -681,27 +789,33 @@ module Net
681
789
  #
682
790
  # Unlike #add, #merge, or #union, the new value is appended to #string.
683
791
  # This may result in a #string which has duplicates or is out-of-order.
684
- def append(object)
685
- tuple = input_to_tuple object
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
686
799
  entry = tuple_to_str tuple
800
+ string unless empty? # write @string before tuple_add
687
801
  tuple_add tuple
688
- @string = -(string ? "#{@string},#{entry}" : entry)
802
+ @string = -(@string ? "#{@string},#{entry}" : entry)
689
803
  self
690
804
  end
691
805
 
692
- # :call-seq: add?(object) -> self or nil
806
+ # :call-seq: add?(element) -> self or nil
693
807
  #
694
808
  # Adds a range or number to the set and returns +self+. Returns +nil+
695
- # when the object is already included in the set.
809
+ # when the element is already included in the set.
696
810
  #
697
811
  # #string will be regenerated. Use #merge to add many elements at once.
698
812
  #
699
813
  # Related: #add, #merge, #union, #include?
700
- def add?(object)
701
- add object unless include? object
814
+ def add?(element)
815
+ add element unless include? element
702
816
  end
703
817
 
704
- # :call-seq: delete(object) -> self
818
+ # :call-seq: delete(element) -> self
705
819
  #
706
820
  # Deletes the given range or number from the set and returns +self+.
707
821
  #
@@ -709,8 +823,8 @@ module Net
709
823
  # many elements at once.
710
824
  #
711
825
  # Related: #delete?, #delete_at, #subtract, #difference
712
- def delete(object)
713
- tuple_subtract input_to_tuple object
826
+ def delete(element)
827
+ tuple_subtract input_to_tuple element
714
828
  normalize!
715
829
  end
716
830
 
@@ -746,8 +860,8 @@ module Net
746
860
  # #string will be regenerated after deletion.
747
861
  #
748
862
  # Related: #delete, #delete_at, #subtract, #difference, #disjoint?
749
- def delete?(object)
750
- tuple = input_to_tuple object
863
+ def delete?(element)
864
+ tuple = input_to_tuple element
751
865
  if tuple.first == tuple.last
752
866
  return unless include_tuple? tuple
753
867
  tuple_subtract tuple
@@ -791,33 +905,31 @@ module Net
791
905
  deleted
792
906
  end
793
907
 
794
- # Merges all of the elements that appear in any of the +inputs+ into the
908
+ # Merges all of the elements that appear in any of the +sets+ into the
795
909
  # set, and returns +self+.
796
910
  #
797
- # The +inputs+ may be any objects that would be accepted by ::new:
798
- # non-zero 32 bit unsigned integers, ranges, <tt>sequence-set</tt>
799
- # formatted strings, other sequence sets, or enumerables containing any of
800
- # these.
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.
801
914
  #
802
- # #string will be regenerated after all inputs have been merged.
915
+ # #string will be regenerated after all sets have been merged.
803
916
  #
804
917
  # Related: #add, #add?, #union
805
- def merge(*inputs)
806
- tuples_add input_to_tuples inputs
918
+ def merge(*sets)
919
+ tuples_add input_to_tuples sets
807
920
  normalize!
808
921
  end
809
922
 
810
- # Removes all of the elements that appear in any of the given +objects+
811
- # from the set, and returns +self+.
923
+ # Removes all of the elements that appear in any of the given +sets+ from
924
+ # the set, and returns +self+.
812
925
  #
813
- # The +objects+ may be any objects that would be accepted by ::new:
814
- # non-zero 32 bit unsigned integers, ranges, <tt>sequence-set</tt>
815
- # formatted strings, other sequence sets, or enumerables containing any of
816
- # these.
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.
817
929
  #
818
930
  # Related: #difference
819
- def subtract(*objects)
820
- tuples_subtract input_to_tuples objects
931
+ def subtract(*sets)
932
+ tuples_subtract input_to_tuples sets
821
933
  normalize!
822
934
  end
823
935
 
@@ -829,21 +941,21 @@ module Net
829
941
  # This is useful when the given order is significant, for example in a
830
942
  # ESEARCH response to IMAP#sort.
831
943
  #
944
+ # See Net::IMAP@Ordered+and+Normalized+Sets.
945
+ #
832
946
  # Related: #each_entry, #elements
833
947
  def entries; each_entry.to_a end
834
948
 
835
949
  # Returns an array of ranges and integers and <tt>:*</tt>.
836
950
  #
837
951
  # The returned elements are sorted and coalesced, even when the input
838
- # #string is not. <tt>*</tt> will sort last. See #normalize.
952
+ # #string is not. <tt>*</tt> will sort last. See #normalize,
953
+ # Net::IMAP@Ordered+and+Normalized+Sets.
839
954
  #
840
955
  # By itself, <tt>*</tt> translates to <tt>:*</tt>. A range containing
841
956
  # <tt>*</tt> translates to an endless range. Use #limit to translate both
842
957
  # cases to a maximum value.
843
958
  #
844
- # If the original input was unordered or contains overlapping ranges, the
845
- # returned ranges will be ordered and coalesced.
846
- #
847
959
  # Net::IMAP::SequenceSet["2,5:9,6,*,12:11"].elements
848
960
  # #=> [2, 5..9, 11..12, :*]
849
961
  #
@@ -854,15 +966,13 @@ module Net
854
966
  # Returns an array of ranges
855
967
  #
856
968
  # The returned elements are sorted and coalesced, even when the input
857
- # #string is not. <tt>*</tt> will sort last. See #normalize.
969
+ # #string is not. <tt>*</tt> will sort last. See #normalize,
970
+ # Net::IMAP@Ordered+and+Normalized+Sets.
858
971
  #
859
972
  # <tt>*</tt> translates to an endless range. By itself, <tt>*</tt>
860
973
  # translates to <tt>:*..</tt>. Use #limit to set <tt>*</tt> to a maximum
861
974
  # value.
862
975
  #
863
- # The returned ranges will be ordered and coalesced, even when the input
864
- # #string is not. <tt>*</tt> will sort last. See #normalize.
865
- #
866
976
  # Net::IMAP::SequenceSet["2,5:9,6,*,12:11"].ranges
867
977
  # #=> [2..2, 5..9, 11..12, :*..]
868
978
  # Net::IMAP::SequenceSet["123,999:*,456:789"].ranges
@@ -874,7 +984,7 @@ module Net
874
984
  # Returns a sorted array of all of the number values in the sequence set.
875
985
  #
876
986
  # The returned numbers are sorted and de-duplicated, even when the input
877
- # #string is not. See #normalize.
987
+ # #string is not. See #normalize, Net::IMAP@Ordered+and+Normalized+Sets.
878
988
  #
879
989
  # Net::IMAP::SequenceSet["2,5:9,6,12:11"].numbers
880
990
  # #=> [2, 5, 6, 7, 8, 9, 11, 12]
@@ -902,23 +1012,23 @@ module Net
902
1012
  # Yields each number or range in #string to the block and returns +self+.
903
1013
  # Returns an enumerator when called without a block.
904
1014
  #
905
- # The entries are yielded in the same order they appear in #tring, with no
906
- # sorting, deduplication, or coalescing. When #string is in its
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
907
1017
  # normalized form, this will yield the same values as #each_element.
908
1018
  #
1019
+ # See Net::IMAP@Ordered+and+Normalized+Sets.
1020
+ #
909
1021
  # Related: #entries, #each_element
910
1022
  def each_entry(&block) # :yields: integer or range or :*
911
1023
  return to_enum(__method__) unless block_given?
912
- return each_element(&block) unless @string
913
- @string.split(",").each do yield tuple_to_entry str_to_tuple _1 end
914
- self
1024
+ each_entry_tuple do yield tuple_to_entry _1 end
915
1025
  end
916
1026
 
917
1027
  # Yields each number or range (or <tt>:*</tt>) in #elements to the block
918
1028
  # and returns self. Returns an enumerator when called without a block.
919
1029
  #
920
1030
  # The returned numbers are sorted and de-duplicated, even when the input
921
- # #string is not. See #normalize.
1031
+ # #string is not. See #normalize, Net::IMAP@Ordered+and+Normalized+Sets.
922
1032
  #
923
1033
  # Related: #elements, #each_entry
924
1034
  def each_element # :yields: integer or range or :*
@@ -929,6 +1039,16 @@ module Net
929
1039
 
930
1040
  private
931
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
+
932
1052
  def tuple_to_entry((min, max))
933
1053
  if min == STAR_INT then :*
934
1054
  elsif max == STAR_INT then min..
@@ -960,19 +1080,36 @@ module Net
960
1080
  # Returns an enumerator when called without a block (even if the set
961
1081
  # contains <tt>*</tt>).
962
1082
  #
963
- # Related: #numbers
1083
+ # Related: #numbers, #each_ordered_number
964
1084
  def each_number(&block) # :yields: integer
965
1085
  return to_enum(__method__) unless block_given?
966
1086
  raise RangeError, '%s contains "*"' % [self.class] if include_star?
967
- each_element do |elem|
968
- case elem
969
- when Range then elem.each(&block)
970
- when Integer then block.(elem)
971
- end
972
- end
1087
+ @tuples.each do each_number_in_tuple _1, _2, &block end
973
1088
  self
974
1089
  end
975
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
+
976
1113
  # Returns a Set with all of the #numbers in the sequence set.
977
1114
  #
978
1115
  # If the set contains a <tt>*</tt>, RangeError will be raised.
@@ -984,8 +1121,10 @@ module Net
984
1121
 
985
1122
  # Returns the count of #numbers in the set.
986
1123
  #
987
- # If <tt>*</tt> and <tt>2**32 - 1</tt> (the maximum 32-bit unsigned
988
- # integer value) are both in the set, they will only be counted once.
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
989
1128
  def count
990
1129
  @tuples.sum(@tuples.count) { _2 - _1 } +
991
1130
  (include_star? && include?(UINT32_MAX) ? -1 : 0)
@@ -993,33 +1132,87 @@ module Net
993
1132
 
994
1133
  alias size count
995
1134
 
996
- # Returns the index of +number+ in the set, or +nil+ if +number+ isn't in
997
- # the set.
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.
998
1165
  #
999
- # Related: #[]
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
1000
1178
  def find_index(number)
1001
1179
  number = to_tuple_int number
1002
- each_tuple_with_index do |min, max, idx_min|
1180
+ each_tuple_with_index(@tuples) do |min, max, idx_min|
1003
1181
  number < min and return nil
1004
1182
  number <= max and return from_tuple_int(idx_min + (number - min))
1005
1183
  end
1006
1184
  nil
1007
1185
  end
1008
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
+
1009
1201
  private
1010
1202
 
1011
- def each_tuple_with_index
1203
+ def each_tuple_with_index(tuples)
1012
1204
  idx_min = 0
1013
- @tuples.each do |min, max|
1014
- yield min, max, idx_min, (idx_max = idx_min + (max - min))
1205
+ tuples.each do |min, max|
1206
+ idx_max = idx_min + (max - min)
1207
+ yield min, max, idx_min, idx_max
1015
1208
  idx_min = idx_max + 1
1016
1209
  end
1017
1210
  idx_min
1018
1211
  end
1019
1212
 
1020
- def reverse_each_tuple_with_index
1213
+ def reverse_each_tuple_with_index(tuples)
1021
1214
  idx_max = -1
1022
- @tuples.reverse_each do |min, max|
1215
+ tuples.reverse_each do |min, max|
1023
1216
  yield min, max, (idx_min = idx_max - (max - min)), idx_max
1024
1217
  idx_max = idx_min - 1
1025
1218
  end
@@ -1030,18 +1223,38 @@ module Net
1030
1223
 
1031
1224
  # :call-seq: at(index) -> integer or nil
1032
1225
  #
1033
- # Returns a number from +self+, without modifying the set. Behaves the
1034
- # same as #[], except that #at only allows a single integer argument.
1226
+ # Returns the number at the given +index+ in the sorted set, without
1227
+ # modifying the set.
1035
1228
  #
1036
- # Related: #[], #slice
1229
+ # +index+ is interpreted the same as in #[], except that #at only allows a
1230
+ # single integer argument.
1231
+ #
1232
+ # Related: #[], #slice, #ordered_at
1037
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)
1038
1251
  index = Integer(index.to_int)
1039
1252
  if index.negative?
1040
- reverse_each_tuple_with_index do |min, max, idx_min, idx_max|
1253
+ reverse_each_tuple_with_index(tuples) do |min, max, idx_min, idx_max|
1041
1254
  idx_min <= index and return from_tuple_int(min + (index - idx_min))
1042
1255
  end
1043
1256
  else
1044
- each_tuple_with_index do |min, _, idx_min, idx_max|
1257
+ each_tuple_with_index(tuples) do |min, _, idx_min, idx_max|
1045
1258
  index <= idx_max and return from_tuple_int(min + (index - idx_min))
1046
1259
  end
1047
1260
  end
@@ -1056,17 +1269,18 @@ module Net
1056
1269
  # seqset[range] -> sequence set or nil
1057
1270
  # slice(range) -> sequence set or nil
1058
1271
  #
1059
- # Returns a number or a subset from +self+, without modifying the set.
1272
+ # Returns a number or a subset from the _sorted_ set, without modifying
1273
+ # the set.
1060
1274
  #
1061
1275
  # When an Integer argument +index+ is given, the number at offset +index+
1062
- # is returned:
1276
+ # in the sorted set is returned:
1063
1277
  #
1064
1278
  # set = Net::IMAP::SequenceSet["10:15,20:23,26"]
1065
1279
  # set[0] #=> 10
1066
1280
  # set[5] #=> 15
1067
1281
  # set[10] #=> 26
1068
1282
  #
1069
- # If +index+ is negative, it counts relative to the end of +self+:
1283
+ # If +index+ is negative, it counts relative to the end of the sorted set:
1070
1284
  # set = Net::IMAP::SequenceSet["10:15,20:23,26"]
1071
1285
  # set[-1] #=> 26
1072
1286
  # set[-3] #=> 22
@@ -1078,13 +1292,14 @@ module Net
1078
1292
  # set[11] #=> nil
1079
1293
  # set[-12] #=> nil
1080
1294
  #
1081
- # The result is based on the normalized set—sorted and de-duplicatednot
1082
- # on the assigned value of #string.
1295
+ # The result is based on the sorted and de-duplicated set, not on the
1296
+ # ordered #entries in #string.
1083
1297
  #
1084
1298
  # set = Net::IMAP::SequenceSet["12,20:23,11:16,21"]
1085
1299
  # set[0] #=> 11
1086
1300
  # set[-1] #=> 23
1087
1301
  #
1302
+ # Related: #at
1088
1303
  def [](index, length = nil)
1089
1304
  if length then slice_length(index, length)
1090
1305
  elsif index.is_a?(Range) then slice_range(index)
@@ -1107,20 +1322,76 @@ module Net
1107
1322
  def slice_range(range)
1108
1323
  first = range.begin || 0
1109
1324
  last = range.end || -1
1110
- last -= 1 if range.exclude_end? && range.end && last != STAR_INT
1325
+ if range.exclude_end?
1326
+ return remain_frozen_empty if last.zero?
1327
+ last -= 1 if range.end && last != STAR_INT
1328
+ end
1111
1329
  if (first * last).positive? && last < first
1112
- SequenceSet.empty
1330
+ remain_frozen_empty
1113
1331
  elsif (min = at(first))
1114
1332
  max = at(last)
1333
+ max = :* if max.nil?
1115
1334
  if max == :* then self & (min..)
1116
1335
  elsif min <= max then self & (min..max)
1117
- else SequenceSet.empty
1336
+ else remain_frozen_empty
1118
1337
  end
1119
1338
  end
1120
1339
  end
1121
1340
 
1122
1341
  public
1123
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
+
1124
1395
  # Returns a frozen SequenceSet with <tt>*</tt> converted to +max+, numbers
1125
1396
  # and ranges over +max+ removed, and ranges containing +max+ converted to
1126
1397
  # end at +max+.
@@ -1138,6 +1409,7 @@ module Net
1138
1409
  # Net::IMAP::SequenceSet["500:*"].limit(max: 37)
1139
1410
  # #=> Net::IMAP::SequenceSet["37"]
1140
1411
  #
1412
+ # Related: #limit!
1141
1413
  def limit(max:)
1142
1414
  max = to_tuple_int(max)
1143
1415
  if empty? then self.class.empty
@@ -1179,6 +1451,7 @@ module Net
1179
1451
  #
1180
1452
  # The returned set's #string is sorted and deduplicated. Adjacent or
1181
1453
  # overlapping elements will be merged into a single larger range.
1454
+ # See Net::IMAP@Ordered+and+Normalized+Sets.
1182
1455
  #
1183
1456
  # Net::IMAP::SequenceSet["1:5,3:7,10:9,10:11"].normalize
1184
1457
  # #=> Net::IMAP::SequenceSet["1:7,9:11"]
@@ -1191,7 +1464,7 @@ module Net
1191
1464
  end
1192
1465
 
1193
1466
  # Resets #string to be sorted, deduplicated, and coalesced. Returns
1194
- # +self+.
1467
+ # +self+. See Net::IMAP@Ordered+and+Normalized+Sets.
1195
1468
  #
1196
1469
  # Related: #normalize, #normalized_string
1197
1470
  def normalize!
@@ -1201,11 +1474,13 @@ module Net
1201
1474
 
1202
1475
  # Returns a normalized +sequence-set+ string representation, sorted
1203
1476
  # and deduplicated. Adjacent or overlapping elements will be merged into
1204
- # a single larger range. Returns +nil+ when the set is empty.
1477
+ # a single larger range. See Net::IMAP@Ordered+and+Normalized+Sets.
1205
1478
  #
1206
1479
  # Net::IMAP::SequenceSet["1:5,3:7,10:9,10:11"].normalized_string
1207
1480
  # #=> "1:7,9:11"
1208
1481
  #
1482
+ # Returns +nil+ when the set is empty.
1483
+ #
1209
1484
  # Related: #normalize!, #normalize
1210
1485
  def normalized_string
1211
1486
  @tuples.empty? ? nil : -@tuples.map { tuple_to_str _1 }.join(",")
@@ -1235,6 +1510,18 @@ module Net
1235
1510
  imap.__send__(:put_string, valid_string)
1236
1511
  end
1237
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
+
1238
1525
  protected
1239
1526
 
1240
1527
  attr_reader :tuples # :nodoc:
@@ -1242,6 +1529,7 @@ module Net
1242
1529
  private
1243
1530
 
1244
1531
  def remain_frozen(set) frozen? ? set.freeze : set end
1532
+ def remain_frozen_empty; frozen? ? SequenceSet.empty : SequenceSet.new end
1245
1533
 
1246
1534
  # frozen clones are shallow copied
1247
1535
  def initialize_clone(other)
@@ -1254,29 +1542,30 @@ module Net
1254
1542
  super
1255
1543
  end
1256
1544
 
1257
- def input_to_tuple(obj)
1258
- obj = input_try_convert obj
1259
- case obj
1260
- when *STARS, Integer then [int = to_tuple_int(obj), int]
1261
- when Range then range_to_tuple(obj)
1262
- when String then str_to_tuple(obj)
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)
1263
1551
  else
1264
- raise DataFormatError, "expected number or range, got %p" % [obj]
1552
+ raise DataFormatError, "expected number or range, got %p" % [entry]
1265
1553
  end
1266
1554
  end
1267
1555
 
1268
- def input_to_tuples(obj)
1269
- obj = input_try_convert obj
1270
- case obj
1271
- when *STARS, Integer, Range then [input_to_tuple(obj)]
1272
- when String then str_to_tuples obj
1273
- when SequenceSet then obj.tuples
1274
- when ENUMABLE then obj.flat_map { input_to_tuples _1 }
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 }
1275
1564
  when nil then []
1276
1565
  else
1277
1566
  raise DataFormatError,
1278
1567
  "expected nz-number, range, string, or enumerable; " \
1279
- "got %p" % [obj]
1568
+ "got %p" % [set]
1280
1569
  end
1281
1570
  end
1282
1571
 
@@ -1284,8 +1573,7 @@ module Net
1284
1573
  # String, Set, Array, or... any type of object.
1285
1574
  def input_try_convert(input)
1286
1575
  SequenceSet.try_convert(input) ||
1287
- # Integer.try_convert(input) || # ruby 3.1+
1288
- input.respond_to?(:to_int) && Integer(input.to_int) ||
1576
+ Integer.try_convert(input) ||
1289
1577
  String.try_convert(input) ||
1290
1578
  input
1291
1579
  end
@@ -1317,6 +1605,12 @@ module Net
1317
1605
  range.include?(min) || range.include?(max) || (min..max).cover?(range)
1318
1606
  end
1319
1607
 
1608
+ def modifying!
1609
+ if frozen?
1610
+ raise FrozenError, "can't modify frozen #{self.class}: %p" % [self]
1611
+ end
1612
+ end
1613
+
1320
1614
  def tuples_add(tuples) tuples.each do tuple_add _1 end; self end
1321
1615
  def tuples_subtract(tuples) tuples.each do tuple_subtract _1 end; self end
1322
1616
 
@@ -1331,10 +1625,11 @@ module Net
1331
1625
  # ---------??===lower==|--|==|----|===upper===|-- join until upper
1332
1626
  # ---------??===lower==|--|==|--|=====upper===|-- join to upper
1333
1627
  def tuple_add(tuple)
1628
+ modifying!
1334
1629
  min, max = tuple
1335
1630
  lower, lower_idx = tuple_gte_with_index(min - 1)
1336
- if lower.nil? then tuples << tuple
1337
- elsif (max + 1) < lower.first then tuples.insert(lower_idx, tuple)
1631
+ if lower.nil? then tuples << [min, max]
1632
+ elsif (max + 1) < lower.first then tuples.insert(lower_idx, [min, max])
1338
1633
  else tuple_coalesce(lower, lower_idx, min, max)
1339
1634
  end
1340
1635
  end
@@ -1367,6 +1662,7 @@ module Net
1367
1662
  # -------??=====lower====|--|====|---|====upper====|-- 7. delete until
1368
1663
  # -------??=====lower====|--|====|--|=====upper====|-- 8. delete and trim
1369
1664
  def tuple_subtract(tuple)
1665
+ modifying!
1370
1666
  min, max = tuple
1371
1667
  lower, idx = tuple_gte_with_index(min)
1372
1668
  if lower.nil? then nil # case 1.
@@ -1407,12 +1703,11 @@ module Net
1407
1703
  end
1408
1704
 
1409
1705
  def nz_number(num)
1410
- case num
1411
- when Integer, /\A[1-9]\d*\z/ then num = Integer(num)
1412
- else raise DataFormatError, "%p is not a valid nz-number" % [num]
1413
- end
1414
- NumValidator.ensure_nz_number(num)
1415
- 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
1416
1711
  end
1417
1712
 
1418
1713
  # intentionally defined after the class implementation