net-imap 0.4.12 → 0.4.21

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.
@@ -60,18 +60,20 @@ module Net
60
60
  # set = Net::IMAP::SequenceSet[1, 2, [3..7, 5], 6..10, 2048, 1024]
61
61
  # set.valid_string #=> "1:10,55,1024:2048"
62
62
  #
63
- # == Normalized form
63
+ # == Ordered and Normalized sets
64
64
  #
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.
65
+ # Sometimes the order of the set's members is significant, such as with the
66
+ # +ESORT+, <tt>CONTEXT=SORT</tt>, and +UIDPLUS+ extensions. So, when a
67
+ # sequence set is created by the parser or with a single string value, that
68
+ # #string representation is preserved.
71
69
  #
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
70
+ # Internally, SequenceSet stores a normalized representation which sorts all
71
+ # entries, de-duplicates numbers, and coalesces adjacent or overlapping
72
+ # ranges. Most methods use this normalized representation to achieve
73
+ # <tt>O(lg n)</tt> porformance. Use #entries or #each_entry to enumerate
74
+ # the set in its original order.
75
+ #
76
+ # Most modification methods convert #string to its normalized form. To
75
77
  # preserve #string order while modifying a set, use #append, #string=, or
76
78
  # #replace.
77
79
  #
@@ -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,7 +178,7 @@ 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
  #
@@ -185,30 +187,41 @@ module Net
185
187
  # - #max: Returns the maximum number in the set.
186
188
  # - #minmax: Returns the minimum and maximum 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,17 +231,25 @@ 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+
234
255
  # - #limit: Returns a copy of +self+ which has replaced <tt>*</tt> with a
@@ -237,28 +258,39 @@ module Net
237
258
  # === Methods for Assigning
238
259
  # These methods add or replace elements in +self+.
239
260
  #
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
261
+ # <i>Normalized (sorted and coalesced):</i>
262
+ #
263
+ # These methods always update #string to be fully sorted and coalesced.
264
+ #
265
+ # - #add (aliased as #<<): Adds a given element to the set; returns +self+.
266
+ # - #add?: If the given element is not fully included the set, adds it and
242
267
  # 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
268
+ # - #merge: Adds all members of the given sets into this set; returns +self+.
269
+ # - #complement!: Replaces the contents of the set with its own #complement.
270
+ #
271
+ # <i>Order preserving:</i>
272
+ #
273
+ # These methods _may_ cause #string to not be sorted or coalesced.
274
+ #
275
+ # - #append: Adds the given entry to the set, appending it to the existing
245
276
  # string, and returns +self+.
246
277
  # - #string=: Assigns a new #string value and replaces #elements to match.
247
278
  # - #replace: Replaces the contents of the set with the contents
248
279
  # of a given object.
249
- # - #complement!: Replaces the contents of the set with its own #complement.
250
280
  #
251
281
  # === Methods for Deleting
252
- # These methods remove elements from +self+.
282
+ # These methods remove elements from +self+, and update #string to be fully
283
+ # sorted and coalesced.
253
284
  #
254
285
  # - #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
286
+ # - #delete: Removes a given element from the set; returns +self+.
287
+ # - #delete?: If the given element is included in the set, removes it and
257
288
  # returns it; otherwise, returns +nil+.
258
289
  # - #delete_at: Removes the number at a given offset.
259
290
  # - #slice!: Removes the number or consecutive numbers at a given offset or
260
291
  # range of offsets.
261
- # - #subtract: Removes each given object from the set; returns +self+.
292
+ # - #subtract: Removes all members of the given sets from this set; returns
293
+ # +self+.
262
294
  # - #limit!: Replaces <tt>*</tt> with a given maximum value and removes all
263
295
  # members over that maximum; returns +self+.
264
296
  #
@@ -295,16 +327,19 @@ module Net
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"
@@ -641,7 +676,7 @@ module Net
641
676
  #
642
677
  # <tt>(seqset ^ other)</tt> is equivalent to <tt>((seqset | other) -
643
678
  # (seqset & other))</tt>.
644
- def ^(other) remain_frozen (self | other).subtract(self & other) end
679
+ def ^(other) remain_frozen (dup | other).subtract(self & other) end
645
680
  alias xor :^
646
681
 
647
682
  # :call-seq:
@@ -663,7 +698,7 @@ module Net
663
698
  alias complement :~
664
699
 
665
700
  # :call-seq:
666
- # add(object) -> self
701
+ # add(element) -> self
667
702
  # self << other -> self
668
703
  #
669
704
  # Adds a range or number to the set and returns +self+.
@@ -671,8 +706,8 @@ module Net
671
706
  # #string will be regenerated. Use #merge to add many elements at once.
672
707
  #
673
708
  # Related: #add?, #merge, #union
674
- def add(object)
675
- tuple_add input_to_tuple object
709
+ def add(element)
710
+ tuple_add input_to_tuple element
676
711
  normalize!
677
712
  end
678
713
  alias << add
@@ -681,27 +716,29 @@ module Net
681
716
  #
682
717
  # Unlike #add, #merge, or #union, the new value is appended to #string.
683
718
  # This may result in a #string which has duplicates or is out-of-order.
684
- def append(object)
685
- tuple = input_to_tuple object
719
+ def append(entry)
720
+ modifying!
721
+ tuple = input_to_tuple entry
686
722
  entry = tuple_to_str tuple
723
+ string unless empty? # write @string before tuple_add
687
724
  tuple_add tuple
688
- @string = -(string ? "#{@string},#{entry}" : entry)
725
+ @string = -(@string ? "#{@string},#{entry}" : entry)
689
726
  self
690
727
  end
691
728
 
692
- # :call-seq: add?(object) -> self or nil
729
+ # :call-seq: add?(element) -> self or nil
693
730
  #
694
731
  # Adds a range or number to the set and returns +self+. Returns +nil+
695
- # when the object is already included in the set.
732
+ # when the element is already included in the set.
696
733
  #
697
734
  # #string will be regenerated. Use #merge to add many elements at once.
698
735
  #
699
736
  # Related: #add, #merge, #union, #include?
700
- def add?(object)
701
- add object unless include? object
737
+ def add?(element)
738
+ add element unless include? element
702
739
  end
703
740
 
704
- # :call-seq: delete(object) -> self
741
+ # :call-seq: delete(element) -> self
705
742
  #
706
743
  # Deletes the given range or number from the set and returns +self+.
707
744
  #
@@ -709,8 +746,8 @@ module Net
709
746
  # many elements at once.
710
747
  #
711
748
  # Related: #delete?, #delete_at, #subtract, #difference
712
- def delete(object)
713
- tuple_subtract input_to_tuple object
749
+ def delete(element)
750
+ tuple_subtract input_to_tuple element
714
751
  normalize!
715
752
  end
716
753
 
@@ -746,8 +783,8 @@ module Net
746
783
  # #string will be regenerated after deletion.
747
784
  #
748
785
  # Related: #delete, #delete_at, #subtract, #difference, #disjoint?
749
- def delete?(object)
750
- tuple = input_to_tuple object
786
+ def delete?(element)
787
+ tuple = input_to_tuple element
751
788
  if tuple.first == tuple.last
752
789
  return unless include_tuple? tuple
753
790
  tuple_subtract tuple
@@ -791,33 +828,31 @@ module Net
791
828
  deleted
792
829
  end
793
830
 
794
- # Merges all of the elements that appear in any of the +inputs+ into the
831
+ # Merges all of the elements that appear in any of the +sets+ into the
795
832
  # set, and returns +self+.
796
833
  #
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.
834
+ # The +sets+ may be any objects that would be accepted by ::new: non-zero
835
+ # 32 bit unsigned integers, ranges, <tt>sequence-set</tt> formatted
836
+ # strings, other sequence sets, or enumerables containing any of these.
801
837
  #
802
- # #string will be regenerated after all inputs have been merged.
838
+ # #string will be regenerated after all sets have been merged.
803
839
  #
804
840
  # Related: #add, #add?, #union
805
- def merge(*inputs)
806
- tuples_add input_to_tuples inputs
841
+ def merge(*sets)
842
+ tuples_add input_to_tuples sets
807
843
  normalize!
808
844
  end
809
845
 
810
- # Removes all of the elements that appear in any of the given +objects+
811
- # from the set, and returns +self+.
846
+ # Removes all of the elements that appear in any of the given +sets+ from
847
+ # the set, and returns +self+.
812
848
  #
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.
849
+ # The +sets+ may be any objects that would be accepted by ::new: non-zero
850
+ # 32 bit unsigned integers, ranges, <tt>sequence-set</tt> formatted
851
+ # strings, other sequence sets, or enumerables containing any of these.
817
852
  #
818
853
  # Related: #difference
819
- def subtract(*objects)
820
- tuples_subtract input_to_tuples objects
854
+ def subtract(*sets)
855
+ tuples_subtract input_to_tuples sets
821
856
  normalize!
822
857
  end
823
858
 
@@ -841,8 +876,8 @@ module Net
841
876
  # <tt>*</tt> translates to an endless range. Use #limit to translate both
842
877
  # cases to a maximum value.
843
878
  #
844
- # If the original input was unordered or contains overlapping ranges, the
845
- # returned ranges will be ordered and coalesced.
879
+ # The returned elements will be sorted and coalesced, even when the input
880
+ # #string is not. <tt>*</tt> will sort last. See #normalize.
846
881
  #
847
882
  # Net::IMAP::SequenceSet["2,5:9,6,*,12:11"].elements
848
883
  # #=> [2, 5..9, 11..12, :*]
@@ -860,7 +895,7 @@ module Net
860
895
  # translates to <tt>:*..</tt>. Use #limit to set <tt>*</tt> to a maximum
861
896
  # value.
862
897
  #
863
- # The returned ranges will be ordered and coalesced, even when the input
898
+ # The returned ranges will be sorted and coalesced, even when the input
864
899
  # #string is not. <tt>*</tt> will sort last. See #normalize.
865
900
  #
866
901
  # Net::IMAP::SequenceSet["2,5:9,6,*,12:11"].ranges
@@ -909,9 +944,7 @@ module Net
909
944
  # Related: #entries, #each_element
910
945
  def each_entry(&block) # :yields: integer or range or :*
911
946
  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
947
+ each_entry_tuple do yield tuple_to_entry _1 end
915
948
  end
916
949
 
917
950
  # Yields each number or range (or <tt>:*</tt>) in #elements to the block
@@ -929,6 +962,16 @@ module Net
929
962
 
930
963
  private
931
964
 
965
+ def each_entry_tuple(&block)
966
+ return to_enum(__method__) unless block_given?
967
+ if @string
968
+ @string.split(",") do block.call str_to_tuple _1 end
969
+ else
970
+ @tuples.each(&block)
971
+ end
972
+ self
973
+ end
974
+
932
975
  def tuple_to_entry((min, max))
933
976
  if min == STAR_INT then :*
934
977
  elsif max == STAR_INT then min..
@@ -960,19 +1003,36 @@ module Net
960
1003
  # Returns an enumerator when called without a block (even if the set
961
1004
  # contains <tt>*</tt>).
962
1005
  #
963
- # Related: #numbers
1006
+ # Related: #numbers, #each_ordered_number
964
1007
  def each_number(&block) # :yields: integer
965
1008
  return to_enum(__method__) unless block_given?
966
1009
  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
1010
+ @tuples.each do each_number_in_tuple _1, _2, &block end
973
1011
  self
974
1012
  end
975
1013
 
1014
+ # Yields each number in #entries to the block and returns self.
1015
+ # If the set contains a <tt>*</tt>, RangeError will be raised.
1016
+ #
1017
+ # Returns an enumerator when called without a block (even if the set
1018
+ # contains <tt>*</tt>).
1019
+ #
1020
+ # Related: #entries, #each_number
1021
+ def each_ordered_number(&block)
1022
+ return to_enum(__method__) unless block_given?
1023
+ raise RangeError, '%s contains "*"' % [self.class] if include_star?
1024
+ each_entry_tuple do each_number_in_tuple _1, _2, &block end
1025
+ end
1026
+
1027
+ private def each_number_in_tuple(min, max, &block)
1028
+ if min == STAR_INT then yield :*
1029
+ elsif min == max then yield min
1030
+ elsif max != STAR_INT then (min..max).each(&block)
1031
+ else
1032
+ raise RangeError, "#{SequenceSet} cannot enumerate range with '*'"
1033
+ end
1034
+ end
1035
+
976
1036
  # Returns a Set with all of the #numbers in the sequence set.
977
1037
  #
978
1038
  # If the set contains a <tt>*</tt>, RangeError will be raised.
@@ -984,8 +1044,10 @@ module Net
984
1044
 
985
1045
  # Returns the count of #numbers in the set.
986
1046
  #
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.
1047
+ # <tt>*</tt> will be counted as <tt>2**32 - 1</tt> (the maximum 32-bit
1048
+ # unsigned integer value).
1049
+ #
1050
+ # Related: #count_with_duplicates
989
1051
  def count
990
1052
  @tuples.sum(@tuples.count) { _2 - _1 } +
991
1053
  (include_star? && include?(UINT32_MAX) ? -1 : 0)
@@ -993,33 +1055,87 @@ module Net
993
1055
 
994
1056
  alias size count
995
1057
 
996
- # Returns the index of +number+ in the set, or +nil+ if +number+ isn't in
997
- # the set.
1058
+ # Returns the count of numbers in the ordered #entries, including any
1059
+ # repeated numbers.
1060
+ #
1061
+ # <tt>*</tt> will be counted as <tt>2**32 - 1</tt> (the maximum 32-bit
1062
+ # unsigned integer value).
1063
+ #
1064
+ # When #string is normalized, this behaves the same as #count.
1065
+ #
1066
+ # Related: #entries, #count_duplicates, #has_duplicates?
1067
+ def count_with_duplicates
1068
+ return count unless @string
1069
+ each_entry_tuple.sum {|min, max|
1070
+ max - min + ((max == STAR_INT && min != STAR_INT) ? 0 : 1)
1071
+ }
1072
+ end
1073
+
1074
+ # Returns the count of repeated numbers in the ordered #entries, the
1075
+ # difference between #count_with_duplicates and #count.
1076
+ #
1077
+ # When #string is normalized, this is zero.
998
1078
  #
999
- # Related: #[]
1079
+ # Related: #entries, #count_with_duplicates, #has_duplicates?
1080
+ def count_duplicates
1081
+ return 0 unless @string
1082
+ count_with_duplicates - count
1083
+ end
1084
+
1085
+ # :call-seq: has_duplicates? -> true | false
1086
+ #
1087
+ # Returns whether or not the ordered #entries repeat any numbers.
1088
+ #
1089
+ # Always returns +false+ when #string is normalized.
1090
+ #
1091
+ # Related: #entries, #count_with_duplicates, #count_duplicates?
1092
+ def has_duplicates?
1093
+ return false unless @string
1094
+ count_with_duplicates != count
1095
+ end
1096
+
1097
+ # Returns the (sorted and deduplicated) index of +number+ in the set, or
1098
+ # +nil+ if +number+ isn't in the set.
1099
+ #
1100
+ # Related: #[], #at, #find_ordered_index
1000
1101
  def find_index(number)
1001
1102
  number = to_tuple_int number
1002
- each_tuple_with_index do |min, max, idx_min|
1103
+ each_tuple_with_index(@tuples) do |min, max, idx_min|
1003
1104
  number < min and return nil
1004
1105
  number <= max and return from_tuple_int(idx_min + (number - min))
1005
1106
  end
1006
1107
  nil
1007
1108
  end
1008
1109
 
1110
+ # Returns the first index of +number+ in the ordered #entries, or
1111
+ # +nil+ if +number+ isn't in the set.
1112
+ #
1113
+ # Related: #find_index
1114
+ def find_ordered_index(number)
1115
+ number = to_tuple_int number
1116
+ each_tuple_with_index(each_entry_tuple) do |min, max, idx_min|
1117
+ if min <= number && number <= max
1118
+ return from_tuple_int(idx_min + (number - min))
1119
+ end
1120
+ end
1121
+ nil
1122
+ end
1123
+
1009
1124
  private
1010
1125
 
1011
- def each_tuple_with_index
1126
+ def each_tuple_with_index(tuples)
1012
1127
  idx_min = 0
1013
- @tuples.each do |min, max|
1014
- yield min, max, idx_min, (idx_max = idx_min + (max - min))
1128
+ tuples.each do |min, max|
1129
+ idx_max = idx_min + (max - min)
1130
+ yield min, max, idx_min, idx_max
1015
1131
  idx_min = idx_max + 1
1016
1132
  end
1017
1133
  idx_min
1018
1134
  end
1019
1135
 
1020
- def reverse_each_tuple_with_index
1136
+ def reverse_each_tuple_with_index(tuples)
1021
1137
  idx_max = -1
1022
- @tuples.reverse_each do |min, max|
1138
+ tuples.reverse_each do |min, max|
1023
1139
  yield min, max, (idx_min = idx_max - (max - min)), idx_max
1024
1140
  idx_max = idx_min - 1
1025
1141
  end
@@ -1030,18 +1146,38 @@ module Net
1030
1146
 
1031
1147
  # :call-seq: at(index) -> integer or nil
1032
1148
  #
1033
- # Returns a number from +self+, without modifying the set. Behaves the
1034
- # same as #[], except that #at only allows a single integer argument.
1149
+ # Returns the number at the given +index+ in the sorted set, without
1150
+ # modifying the set.
1151
+ #
1152
+ # +index+ is interpreted the same as in #[], except that #at only allows a
1153
+ # single integer argument.
1035
1154
  #
1036
- # Related: #[], #slice
1155
+ # Related: #[], #slice, #ordered_at
1037
1156
  def at(index)
1157
+ lookup_number_by_tuple_index(tuples, index)
1158
+ end
1159
+
1160
+ # :call-seq: ordered_at(index) -> integer or nil
1161
+ #
1162
+ # Returns the number at the given +index+ in the ordered #entries, without
1163
+ # modifying the set.
1164
+ #
1165
+ # +index+ is interpreted the same as in #at (and #[]), except that
1166
+ # #ordered_at applies to the ordered #entries, not the sorted set.
1167
+ #
1168
+ # Related: #[], #slice, #ordered_at
1169
+ def ordered_at(index)
1170
+ lookup_number_by_tuple_index(each_entry_tuple, index)
1171
+ end
1172
+
1173
+ private def lookup_number_by_tuple_index(tuples, index)
1038
1174
  index = Integer(index.to_int)
1039
1175
  if index.negative?
1040
- reverse_each_tuple_with_index do |min, max, idx_min, idx_max|
1176
+ reverse_each_tuple_with_index(tuples) do |min, max, idx_min, idx_max|
1041
1177
  idx_min <= index and return from_tuple_int(min + (index - idx_min))
1042
1178
  end
1043
1179
  else
1044
- each_tuple_with_index do |min, _, idx_min, idx_max|
1180
+ each_tuple_with_index(tuples) do |min, _, idx_min, idx_max|
1045
1181
  index <= idx_max and return from_tuple_int(min + (index - idx_min))
1046
1182
  end
1047
1183
  end
@@ -1056,17 +1192,18 @@ module Net
1056
1192
  # seqset[range] -> sequence set or nil
1057
1193
  # slice(range) -> sequence set or nil
1058
1194
  #
1059
- # Returns a number or a subset from +self+, without modifying the set.
1195
+ # Returns a number or a subset from the _sorted_ set, without modifying
1196
+ # the set.
1060
1197
  #
1061
1198
  # When an Integer argument +index+ is given, the number at offset +index+
1062
- # is returned:
1199
+ # in the sorted set is returned:
1063
1200
  #
1064
1201
  # set = Net::IMAP::SequenceSet["10:15,20:23,26"]
1065
1202
  # set[0] #=> 10
1066
1203
  # set[5] #=> 15
1067
1204
  # set[10] #=> 26
1068
1205
  #
1069
- # If +index+ is negative, it counts relative to the end of +self+:
1206
+ # If +index+ is negative, it counts relative to the end of the sorted set:
1070
1207
  # set = Net::IMAP::SequenceSet["10:15,20:23,26"]
1071
1208
  # set[-1] #=> 26
1072
1209
  # set[-3] #=> 22
@@ -1078,13 +1215,14 @@ module Net
1078
1215
  # set[11] #=> nil
1079
1216
  # set[-12] #=> nil
1080
1217
  #
1081
- # The result is based on the normalized set—sorted and de-duplicatednot
1082
- # on the assigned value of #string.
1218
+ # The result is based on the sorted and de-duplicated set, not on the
1219
+ # ordered #entries in #string.
1083
1220
  #
1084
1221
  # set = Net::IMAP::SequenceSet["12,20:23,11:16,21"]
1085
1222
  # set[0] #=> 11
1086
1223
  # set[-1] #=> 23
1087
1224
  #
1225
+ # Related: #at
1088
1226
  def [](index, length = nil)
1089
1227
  if length then slice_length(index, length)
1090
1228
  elsif index.is_a?(Range) then slice_range(index)
@@ -1107,14 +1245,18 @@ module Net
1107
1245
  def slice_range(range)
1108
1246
  first = range.begin || 0
1109
1247
  last = range.end || -1
1110
- last -= 1 if range.exclude_end? && range.end && last != STAR_INT
1248
+ if range.exclude_end?
1249
+ return remain_frozen_empty if last.zero?
1250
+ last -= 1 if range.end && last != STAR_INT
1251
+ end
1111
1252
  if (first * last).positive? && last < first
1112
- SequenceSet.empty
1253
+ remain_frozen_empty
1113
1254
  elsif (min = at(first))
1114
1255
  max = at(last)
1256
+ max = :* if max.nil?
1115
1257
  if max == :* then self & (min..)
1116
1258
  elsif min <= max then self & (min..max)
1117
- else SequenceSet.empty
1259
+ else remain_frozen_empty
1118
1260
  end
1119
1261
  end
1120
1262
  end
@@ -1242,6 +1384,7 @@ module Net
1242
1384
  private
1243
1385
 
1244
1386
  def remain_frozen(set) frozen? ? set.freeze : set end
1387
+ def remain_frozen_empty; frozen? ? SequenceSet.empty : SequenceSet.new end
1245
1388
 
1246
1389
  # frozen clones are shallow copied
1247
1390
  def initialize_clone(other)
@@ -1254,29 +1397,29 @@ module Net
1254
1397
  super
1255
1398
  end
1256
1399
 
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)
1400
+ def input_to_tuple(entry)
1401
+ entry = input_try_convert entry
1402
+ case entry
1403
+ when *STARS, Integer then [int = to_tuple_int(entry), int]
1404
+ when Range then range_to_tuple(entry)
1405
+ when String then str_to_tuple(entry)
1263
1406
  else
1264
- raise DataFormatError, "expected number or range, got %p" % [obj]
1407
+ raise DataFormatError, "expected number or range, got %p" % [entry]
1265
1408
  end
1266
1409
  end
1267
1410
 
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 }
1411
+ def input_to_tuples(set)
1412
+ set = input_try_convert set
1413
+ case set
1414
+ when *STARS, Integer, Range then [input_to_tuple(set)]
1415
+ when String then str_to_tuples set
1416
+ when SequenceSet then set.tuples
1417
+ when ENUMABLE then set.flat_map { input_to_tuples _1 }
1275
1418
  when nil then []
1276
1419
  else
1277
1420
  raise DataFormatError,
1278
1421
  "expected nz-number, range, string, or enumerable; " \
1279
- "got %p" % [obj]
1422
+ "got %p" % [set]
1280
1423
  end
1281
1424
  end
1282
1425
 
@@ -1317,6 +1460,12 @@ module Net
1317
1460
  range.include?(min) || range.include?(max) || (min..max).cover?(range)
1318
1461
  end
1319
1462
 
1463
+ def modifying!
1464
+ if frozen?
1465
+ raise FrozenError, "can't modify frozen #{self.class}: %p" % [self]
1466
+ end
1467
+ end
1468
+
1320
1469
  def tuples_add(tuples) tuples.each do tuple_add _1 end; self end
1321
1470
  def tuples_subtract(tuples) tuples.each do tuple_subtract _1 end; self end
1322
1471
 
@@ -1331,10 +1480,11 @@ module Net
1331
1480
  # ---------??===lower==|--|==|----|===upper===|-- join until upper
1332
1481
  # ---------??===lower==|--|==|--|=====upper===|-- join to upper
1333
1482
  def tuple_add(tuple)
1483
+ modifying!
1334
1484
  min, max = tuple
1335
1485
  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)
1486
+ if lower.nil? then tuples << [min, max]
1487
+ elsif (max + 1) < lower.first then tuples.insert(lower_idx, [min, max])
1338
1488
  else tuple_coalesce(lower, lower_idx, min, max)
1339
1489
  end
1340
1490
  end
@@ -1367,6 +1517,7 @@ module Net
1367
1517
  # -------??=====lower====|--|====|---|====upper====|-- 7. delete until
1368
1518
  # -------??=====lower====|--|====|--|=====upper====|-- 8. delete and trim
1369
1519
  def tuple_subtract(tuple)
1520
+ modifying!
1370
1521
  min, max = tuple
1371
1522
  lower, idx = tuple_gte_with_index(min)
1372
1523
  if lower.nil? then nil # case 1.