net-imap 0.4.9.1 → 0.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/BSDL +22 -0
  3. data/COPYING +56 -0
  4. data/Gemfile +12 -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 +61 -0
  13. data/lib/net/imap/config.rb +470 -0
  14. data/lib/net/imap/data_encoding.rb +4 -4
  15. data/lib/net/imap/data_lite.rb +226 -0
  16. data/lib/net/imap/deprecated_client_options.rb +9 -6
  17. data/lib/net/imap/errors.rb +7 -1
  18. data/lib/net/imap/esearch_result.rb +180 -0
  19. data/lib/net/imap/fetch_data.rb +126 -47
  20. data/lib/net/imap/flags.rb +1 -1
  21. data/lib/net/imap/response_data.rb +126 -239
  22. data/lib/net/imap/response_parser/parser_utils.rb +11 -6
  23. data/lib/net/imap/response_parser.rb +188 -34
  24. data/lib/net/imap/sasl/anonymous_authenticator.rb +3 -3
  25. data/lib/net/imap/sasl/authentication_exchange.rb +52 -20
  26. data/lib/net/imap/sasl/authenticators.rb +8 -4
  27. data/lib/net/imap/sasl/client_adapter.rb +77 -26
  28. data/lib/net/imap/sasl/cram_md5_authenticator.rb +4 -4
  29. data/lib/net/imap/sasl/digest_md5_authenticator.rb +218 -56
  30. data/lib/net/imap/sasl/external_authenticator.rb +3 -3
  31. data/lib/net/imap/sasl/gs2_header.rb +7 -7
  32. data/lib/net/imap/sasl/login_authenticator.rb +4 -3
  33. data/lib/net/imap/sasl/oauthbearer_authenticator.rb +6 -6
  34. data/lib/net/imap/sasl/plain_authenticator.rb +7 -7
  35. data/lib/net/imap/sasl/protocol_adapters.rb +60 -4
  36. data/lib/net/imap/sasl/scram_authenticator.rb +8 -8
  37. data/lib/net/imap/sasl.rb +8 -5
  38. data/lib/net/imap/sasl_adapter.rb +0 -1
  39. data/lib/net/imap/search_result.rb +4 -8
  40. data/lib/net/imap/sequence_set.rb +239 -88
  41. data/lib/net/imap/stringprep/nameprep.rb +1 -1
  42. data/lib/net/imap/stringprep/trace.rb +4 -4
  43. data/lib/net/imap/uidplus_data.rb +244 -0
  44. data/lib/net/imap/vanished_data.rb +56 -0
  45. data/lib/net/imap.rb +1012 -322
  46. data/net-imap.gemspec +4 -7
  47. data/rakelib/benchmarks.rake +1 -1
  48. data/rakelib/rfcs.rake +2 -0
  49. data/rakelib/string_prep_tables_generator.rb +2 -0
  50. data/sample/net-imap.rb +167 -0
  51. metadata +16 -40
  52. data/.github/dependabot.yml +0 -6
  53. data/.github/workflows/pages.yml +0 -46
  54. data/.github/workflows/test.yml +0 -31
  55. 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
  #
@@ -164,7 +162,7 @@ module Net
164
162
  # - #===:
165
163
  # Returns whether a given object is fully contained within +self+, or
166
164
  # +nil+ if the object cannot be converted to a compatible type.
167
- # - #cover? (aliased as #===):
165
+ # - #cover?:
168
166
  # Returns whether a given object is fully contained within +self+.
169
167
  # - #intersect? (aliased as #overlap?):
170
168
  # Returns whether +self+ and a given object have any common elements.
@@ -185,30 +183,41 @@ module Net
185
183
  # - #max: Returns the maximum number in the set.
186
184
  # - #minmax: Returns the minimum and maximum numbers in the set.
187
185
  #
188
- # <i>Accessing value by offset:</i>
186
+ # <i>Accessing value by offset in sorted set:</i>
189
187
  # - #[] (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
188
+ # given offset or range of offsets in the sorted set.
189
+ # - #at: Returns the number at a given offset in the sorted set.
190
+ # - #find_index: Returns the given number's offset in the sorted set.
191
+ #
192
+ # <i>Accessing value by offset in ordered entries</i>
193
+ # - #ordered_at: Returns the number at a given offset in the ordered entries.
194
+ # - #find_ordered_index: Returns the index of the given number's first
195
+ # occurrence in entries.
193
196
  #
194
197
  # <i>Set cardinality:</i>
195
198
  # - #count (aliased as #size): Returns the count of numbers in the set.
199
+ # Duplicated numbers are not counted.
196
200
  # - #empty?: Returns whether the set has no members. \IMAP syntax does not
197
201
  # allow empty sequence sets.
198
202
  # - #valid?: Returns whether the set has any members.
199
203
  # - #full?: Returns whether the set contains every possible value, including
200
204
  # <tt>*</tt>.
201
205
  #
206
+ # <i>Denormalized properties:</i>
207
+ # - #has_duplicates?: Returns whether the ordered entries repeat any
208
+ # numbers.
209
+ # - #count_duplicates: Returns the count of repeated numbers in the ordered
210
+ # entries.
211
+ # - #count_with_duplicates: Returns the count of numbers in the ordered
212
+ # entries, including any repeated numbers.
213
+ #
202
214
  # === Methods for Iterating
203
215
  #
216
+ # <i>Normalized (sorted and coalesced):</i>
204
217
  # - #each_element: Yields each number and range in the set, sorted and
205
218
  # coalesced, and returns +self+.
206
219
  # - #elements (aliased as #to_a): Returns an Array of every number and range
207
220
  # 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
221
  # - #each_range:
213
222
  # Yields each element in the set as a Range and returns +self+.
214
223
  # - #ranges: Returns an Array of every element in the set, converting
@@ -218,6 +227,14 @@ module Net
218
227
  # ranges into all of their contained numbers.
219
228
  # - #to_set: Returns a Set containing all of the #numbers in the set.
220
229
  #
230
+ # <i>Order preserving:</i>
231
+ # - #each_entry: Yields each number and range in the set, unsorted and
232
+ # without deduplicating numbers or coalescing ranges, and returns +self+.
233
+ # - #entries: Returns an Array of every number and range in the set,
234
+ # unsorted and without deduplicating numbers or coalescing ranges.
235
+ # - #each_ordered_number: Yields each number in the ordered entries and
236
+ # returns +self+.
237
+ #
221
238
  # === Methods for \Set Operations
222
239
  # These methods do not modify +self+.
223
240
  #
@@ -237,19 +254,29 @@ module Net
237
254
  # === Methods for Assigning
238
255
  # These methods add or replace elements in +self+.
239
256
  #
257
+ # <i>Normalized (sorted and coalesced):</i>
258
+ #
259
+ # These methods always update #string to be fully sorted and coalesced.
260
+ #
240
261
  # - #add (aliased as #<<): Adds a given object to the set; returns +self+.
241
262
  # - #add?: If the given object is not an element in the set, adds it and
242
263
  # returns +self+; otherwise, returns +nil+.
243
264
  # - #merge: Merges multiple elements into the set; returns +self+.
265
+ # - #complement!: Replaces the contents of the set with its own #complement.
266
+ #
267
+ # <i>Order preserving:</i>
268
+ #
269
+ # These methods _may_ cause #string to not be sorted or coalesced.
270
+ #
244
271
  # - #append: Adds a given object to the set, appending it to the existing
245
272
  # string, and returns +self+.
246
273
  # - #string=: Assigns a new #string value and replaces #elements to match.
247
274
  # - #replace: Replaces the contents of the set with the contents
248
275
  # of a given object.
249
- # - #complement!: Replaces the contents of the set with its own #complement.
250
276
  #
251
277
  # === Methods for Deleting
252
- # These methods remove elements from +self+.
278
+ # These methods remove elements from +self+, and update #string to be fully
279
+ # sorted and coalesced.
253
280
  #
254
281
  # - #clear: Removes all elements in the set; returns +self+.
255
282
  # - #delete: Removes a given object from the set; returns +self+.
@@ -286,11 +313,7 @@ module Net
286
313
 
287
314
  # valid inputs for "*"
288
315
  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
316
+ private_constant :STARS
294
317
 
295
318
  class << self
296
319
 
@@ -304,7 +327,7 @@ module Net
304
327
  # Use ::new to create a mutable or empty SequenceSet.
305
328
  def [](first, *rest)
306
329
  if rest.empty?
307
- if first.is_a?(SequenceSet) && set.frozen? && set.valid?
330
+ if first.is_a?(SequenceSet) && first.frozen? && first.valid?
308
331
  first
309
332
  else
310
333
  new(first).validate.freeze
@@ -325,7 +348,7 @@ module Net
325
348
  # raised.
326
349
  def try_convert(obj)
327
350
  return obj if obj.is_a?(SequenceSet)
328
- return nil unless respond_to?(:to_sequence_set)
351
+ return nil unless obj.respond_to?(:to_sequence_set)
329
352
  obj = obj.to_sequence_set
330
353
  return obj if obj.is_a?(SequenceSet)
331
354
  raise DataFormatError, "invalid object returned from to_sequence_set"
@@ -389,6 +412,10 @@ module Net
389
412
  # Related: #valid_string, #normalized_string, #to_s
390
413
  def string; @string ||= normalized_string if valid? end
391
414
 
415
+ # Returns an array with #normalized_string when valid and an empty array
416
+ # otherwise.
417
+ def deconstruct; valid? ? [normalized_string] : [] end
418
+
392
419
  # Assigns a new string to #string and resets #elements to match. It
393
420
  # cannot be set to an empty string—assign +nil+ or use #clear instead.
394
421
  # The string is validated but not normalized.
@@ -682,10 +709,12 @@ module Net
682
709
  # Unlike #add, #merge, or #union, the new value is appended to #string.
683
710
  # This may result in a #string which has duplicates or is out-of-order.
684
711
  def append(object)
712
+ modifying!
685
713
  tuple = input_to_tuple object
686
714
  entry = tuple_to_str tuple
715
+ string unless empty? # write @string before tuple_add
687
716
  tuple_add tuple
688
- @string = -(string ? "#{@string},#{entry}" : entry)
717
+ @string = -(@string ? "#{@string},#{entry}" : entry)
689
718
  self
690
719
  end
691
720
 
@@ -841,8 +870,8 @@ module Net
841
870
  # <tt>*</tt> translates to an endless range. Use #limit to translate both
842
871
  # cases to a maximum value.
843
872
  #
844
- # If the original input was unordered or contains overlapping ranges, the
845
- # returned ranges will be ordered and coalesced.
873
+ # The returned elements will be sorted and coalesced, even when the input
874
+ # #string is not. <tt>*</tt> will sort last. See #normalize.
846
875
  #
847
876
  # Net::IMAP::SequenceSet["2,5:9,6,*,12:11"].elements
848
877
  # #=> [2, 5..9, 11..12, :*]
@@ -860,7 +889,7 @@ module Net
860
889
  # translates to <tt>:*..</tt>. Use #limit to set <tt>*</tt> to a maximum
861
890
  # value.
862
891
  #
863
- # The returned ranges will be ordered and coalesced, even when the input
892
+ # The returned ranges will be sorted and coalesced, even when the input
864
893
  # #string is not. <tt>*</tt> will sort last. See #normalize.
865
894
  #
866
895
  # Net::IMAP::SequenceSet["2,5:9,6,*,12:11"].ranges
@@ -902,16 +931,14 @@ module Net
902
931
  # Yields each number or range in #string to the block and returns +self+.
903
932
  # Returns an enumerator when called without a block.
904
933
  #
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
934
+ # The entries are yielded in the same order they appear in #string, with
935
+ # no sorting, deduplication, or coalescing. When #string is in its
907
936
  # normalized form, this will yield the same values as #each_element.
908
937
  #
909
938
  # Related: #entries, #each_element
910
- def each_entry(&block)
939
+ def each_entry(&block) # :yields: integer or range or :*
911
940
  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
941
+ each_entry_tuple do yield tuple_to_entry _1 end
915
942
  end
916
943
 
917
944
  # Yields each number or range (or <tt>:*</tt>) in #elements to the block
@@ -927,7 +954,19 @@ module Net
927
954
  self
928
955
  end
929
956
 
930
- private def tuple_to_entry((min, max))
957
+ private
958
+
959
+ def each_entry_tuple(&block)
960
+ return to_enum(__method__) unless block_given?
961
+ if @string
962
+ @string.split(",") do block.call str_to_tuple _1 end
963
+ else
964
+ @tuples.each(&block)
965
+ end
966
+ self
967
+ end
968
+
969
+ def tuple_to_entry((min, max))
931
970
  if min == STAR_INT then :*
932
971
  elsif max == STAR_INT then min..
933
972
  elsif min == max then min
@@ -935,6 +974,8 @@ module Net
935
974
  end
936
975
  end
937
976
 
977
+ public
978
+
938
979
  # Yields each range in #ranges to the block and returns self.
939
980
  # Returns an enumerator when called without a block.
940
981
  #
@@ -956,19 +997,36 @@ module Net
956
997
  # Returns an enumerator when called without a block (even if the set
957
998
  # contains <tt>*</tt>).
958
999
  #
959
- # Related: #numbers
1000
+ # Related: #numbers, #each_ordered_number
960
1001
  def each_number(&block) # :yields: integer
961
1002
  return to_enum(__method__) unless block_given?
962
1003
  raise RangeError, '%s contains "*"' % [self.class] if include_star?
963
- each_element do |elem|
964
- case elem
965
- when Range then elem.each(&block)
966
- when Integer then block.(elem)
967
- end
968
- end
1004
+ @tuples.each do each_number_in_tuple _1, _2, &block end
969
1005
  self
970
1006
  end
971
1007
 
1008
+ # Yields each number in #entries to the block and returns self.
1009
+ # If the set contains a <tt>*</tt>, RangeError will be raised.
1010
+ #
1011
+ # Returns an enumerator when called without a block (even if the set
1012
+ # contains <tt>*</tt>).
1013
+ #
1014
+ # Related: #entries, #each_number
1015
+ def each_ordered_number(&block)
1016
+ return to_enum(__method__) unless block_given?
1017
+ raise RangeError, '%s contains "*"' % [self.class] if include_star?
1018
+ each_entry_tuple do each_number_in_tuple _1, _2, &block end
1019
+ end
1020
+
1021
+ private def each_number_in_tuple(min, max, &block)
1022
+ if min == STAR_INT then yield :*
1023
+ elsif min == max then yield min
1024
+ elsif max != STAR_INT then (min..max).each(&block)
1025
+ else
1026
+ raise RangeError, "#{SequenceSet} cannot enumerate range with '*'"
1027
+ end
1028
+ end
1029
+
972
1030
  # Returns a Set with all of the #numbers in the sequence set.
973
1031
  #
974
1032
  # If the set contains a <tt>*</tt>, RangeError will be raised.
@@ -980,8 +1038,10 @@ module Net
980
1038
 
981
1039
  # Returns the count of #numbers in the set.
982
1040
  #
983
- # If <tt>*</tt> and <tt>2**32 - 1</tt> (the maximum 32-bit unsigned
984
- # integer value) are both in the set, they will only be counted once.
1041
+ # <tt>*</tt> will be counted as <tt>2**32 - 1</tt> (the maximum 32-bit
1042
+ # unsigned integer value).
1043
+ #
1044
+ # Related: #count_with_duplicates
985
1045
  def count
986
1046
  @tuples.sum(@tuples.count) { _2 - _1 } +
987
1047
  (include_star? && include?(UINT32_MAX) ? -1 : 0)
@@ -989,51 +1049,129 @@ module Net
989
1049
 
990
1050
  alias size count
991
1051
 
992
- # Returns the index of +number+ in the set, or +nil+ if +number+ isn't in
993
- # the set.
1052
+ # Returns the count of numbers in the ordered #entries, including any
1053
+ # repeated numbers.
1054
+ #
1055
+ # <tt>*</tt> will be counted as <tt>2**32 - 1</tt> (the maximum 32-bit
1056
+ # unsigned integer value).
1057
+ #
1058
+ # When #string is normalized, this behaves the same as #count.
1059
+ #
1060
+ # Related: #entries, #count_duplicates, #has_duplicates?
1061
+ def count_with_duplicates
1062
+ return count unless @string
1063
+ each_entry_tuple.sum {|min, max|
1064
+ max - min + ((max == STAR_INT && min != STAR_INT) ? 0 : 1)
1065
+ }
1066
+ end
1067
+
1068
+ # Returns the count of repeated numbers in the ordered #entries, the
1069
+ # difference between #count_with_duplicates and #count.
1070
+ #
1071
+ # When #string is normalized, this is zero.
994
1072
  #
995
- # Related: #[]
1073
+ # Related: #entries, #count_with_duplicates, #has_duplicates?
1074
+ def count_duplicates
1075
+ return 0 unless @string
1076
+ count_with_duplicates - count
1077
+ end
1078
+
1079
+ # :call-seq: has_duplicates? -> true | false
1080
+ #
1081
+ # Returns whether or not the ordered #entries repeat any numbers.
1082
+ #
1083
+ # Always returns +false+ when #string is normalized.
1084
+ #
1085
+ # Related: #entries, #count_with_duplicates, #count_duplicates?
1086
+ def has_duplicates?
1087
+ return false unless @string
1088
+ count_with_duplicates != count
1089
+ end
1090
+
1091
+ # Returns the (sorted and deduplicated) index of +number+ in the set, or
1092
+ # +nil+ if +number+ isn't in the set.
1093
+ #
1094
+ # Related: #[], #at, #find_ordered_index
996
1095
  def find_index(number)
997
1096
  number = to_tuple_int number
998
- each_tuple_with_index do |min, max, idx_min|
1097
+ each_tuple_with_index(@tuples) do |min, max, idx_min|
999
1098
  number < min and return nil
1000
1099
  number <= max and return from_tuple_int(idx_min + (number - min))
1001
1100
  end
1002
1101
  nil
1003
1102
  end
1004
1103
 
1005
- private def each_tuple_with_index
1104
+ # Returns the first index of +number+ in the ordered #entries, or
1105
+ # +nil+ if +number+ isn't in the set.
1106
+ #
1107
+ # Related: #find_index
1108
+ def find_ordered_index(number)
1109
+ number = to_tuple_int number
1110
+ each_tuple_with_index(each_entry_tuple) do |min, max, idx_min|
1111
+ if min <= number && number <= max
1112
+ return from_tuple_int(idx_min + (number - min))
1113
+ end
1114
+ end
1115
+ nil
1116
+ end
1117
+
1118
+ private
1119
+
1120
+ def each_tuple_with_index(tuples)
1006
1121
  idx_min = 0
1007
- @tuples.each do |min, max|
1008
- yield min, max, idx_min, (idx_max = idx_min + (max - min))
1122
+ tuples.each do |min, max|
1123
+ idx_max = idx_min + (max - min)
1124
+ yield min, max, idx_min, idx_max
1009
1125
  idx_min = idx_max + 1
1010
1126
  end
1011
1127
  idx_min
1012
1128
  end
1013
1129
 
1014
- private def reverse_each_tuple_with_index
1130
+ def reverse_each_tuple_with_index(tuples)
1015
1131
  idx_max = -1
1016
- @tuples.reverse_each do |min, max|
1132
+ tuples.reverse_each do |min, max|
1017
1133
  yield min, max, (idx_min = idx_max - (max - min)), idx_max
1018
1134
  idx_max = idx_min - 1
1019
1135
  end
1020
1136
  idx_max
1021
1137
  end
1022
1138
 
1139
+ public
1140
+
1023
1141
  # :call-seq: at(index) -> integer or nil
1024
1142
  #
1025
- # Returns a number from +self+, without modifying the set. Behaves the
1026
- # same as #[], except that #at only allows a single integer argument.
1143
+ # Returns the number at the given +index+ in the sorted set, without
1144
+ # modifying the set.
1145
+ #
1146
+ # +index+ is interpreted the same as in #[], except that #at only allows a
1147
+ # single integer argument.
1027
1148
  #
1028
- # Related: #[], #slice
1149
+ # Related: #[], #slice, #ordered_at
1029
1150
  def at(index)
1151
+ lookup_number_by_tuple_index(tuples, index)
1152
+ end
1153
+
1154
+ # :call-seq: ordered_at(index) -> integer or nil
1155
+ #
1156
+ # Returns the number at the given +index+ in the ordered #entries, without
1157
+ # modifying the set.
1158
+ #
1159
+ # +index+ is interpreted the same as in #at (and #[]), except that
1160
+ # #ordered_at applies to the ordered #entries, not the sorted set.
1161
+ #
1162
+ # Related: #[], #slice, #ordered_at
1163
+ def ordered_at(index)
1164
+ lookup_number_by_tuple_index(each_entry_tuple, index)
1165
+ end
1166
+
1167
+ private def lookup_number_by_tuple_index(tuples, index)
1030
1168
  index = Integer(index.to_int)
1031
1169
  if index.negative?
1032
- reverse_each_tuple_with_index do |min, max, idx_min, idx_max|
1170
+ reverse_each_tuple_with_index(tuples) do |min, max, idx_min, idx_max|
1033
1171
  idx_min <= index and return from_tuple_int(min + (index - idx_min))
1034
1172
  end
1035
1173
  else
1036
- each_tuple_with_index do |min, _, idx_min, idx_max|
1174
+ each_tuple_with_index(tuples) do |min, _, idx_min, idx_max|
1037
1175
  index <= idx_max and return from_tuple_int(min + (index - idx_min))
1038
1176
  end
1039
1177
  end
@@ -1048,17 +1186,18 @@ module Net
1048
1186
  # seqset[range] -> sequence set or nil
1049
1187
  # slice(range) -> sequence set or nil
1050
1188
  #
1051
- # Returns a number or a subset from +self+, without modifying the set.
1189
+ # Returns a number or a subset from the _sorted_ set, without modifying
1190
+ # the set.
1052
1191
  #
1053
1192
  # When an Integer argument +index+ is given, the number at offset +index+
1054
- # is returned:
1193
+ # in the sorted set is returned:
1055
1194
  #
1056
1195
  # set = Net::IMAP::SequenceSet["10:15,20:23,26"]
1057
1196
  # set[0] #=> 10
1058
1197
  # set[5] #=> 15
1059
1198
  # set[10] #=> 26
1060
1199
  #
1061
- # If +index+ is negative, it counts relative to the end of +self+:
1200
+ # If +index+ is negative, it counts relative to the end of the sorted set:
1062
1201
  # set = Net::IMAP::SequenceSet["10:15,20:23,26"]
1063
1202
  # set[-1] #=> 26
1064
1203
  # set[-3] #=> 22
@@ -1070,13 +1209,14 @@ module Net
1070
1209
  # set[11] #=> nil
1071
1210
  # set[-12] #=> nil
1072
1211
  #
1073
- # The result is based on the normalized set—sorted and de-duplicatednot
1074
- # on the assigned value of #string.
1212
+ # The result is based on the sorted and de-duplicated set, not on the
1213
+ # ordered #entries in #string.
1075
1214
  #
1076
1215
  # set = Net::IMAP::SequenceSet["12,20:23,11:16,21"]
1077
1216
  # set[0] #=> 11
1078
1217
  # set[-1] #=> 23
1079
1218
  #
1219
+ # Related: #at
1080
1220
  def [](index, length = nil)
1081
1221
  if length then slice_length(index, length)
1082
1222
  elsif index.is_a?(Range) then slice_range(index)
@@ -1086,7 +1226,9 @@ module Net
1086
1226
 
1087
1227
  alias slice :[]
1088
1228
 
1089
- private def slice_length(start, length)
1229
+ private
1230
+
1231
+ def slice_length(start, length)
1090
1232
  start = Integer(start.to_int)
1091
1233
  length = Integer(length.to_int)
1092
1234
  raise ArgumentError, "length must be positive" unless length.positive?
@@ -1094,7 +1236,7 @@ module Net
1094
1236
  slice_range(start..last)
1095
1237
  end
1096
1238
 
1097
- private def slice_range(range)
1239
+ def slice_range(range)
1098
1240
  first = range.begin || 0
1099
1241
  last = range.end || -1
1100
1242
  last -= 1 if range.exclude_end? && range.end && last != STAR_INT
@@ -1109,6 +1251,8 @@ module Net
1109
1251
  end
1110
1252
  end
1111
1253
 
1254
+ public
1255
+
1112
1256
  # Returns a frozen SequenceSet with <tt>*</tt> converted to +max+, numbers
1113
1257
  # and ranges over +max+ removed, and ranges containing +max+ converted to
1114
1258
  # end at +max+.
@@ -1259,7 +1403,8 @@ module Net
1259
1403
  when *STARS, Integer, Range then [input_to_tuple(obj)]
1260
1404
  when String then str_to_tuples obj
1261
1405
  when SequenceSet then obj.tuples
1262
- when ENUMABLE then obj.flat_map { input_to_tuples _1 }
1406
+ when Set then obj.map { [to_tuple_int(_1)] * 2 }
1407
+ when Array then obj.flat_map { input_to_tuples _1 }
1263
1408
  when nil then []
1264
1409
  else
1265
1410
  raise DataFormatError,
@@ -1272,8 +1417,7 @@ module Net
1272
1417
  # String, Set, Array, or... any type of object.
1273
1418
  def input_try_convert(input)
1274
1419
  SequenceSet.try_convert(input) ||
1275
- # Integer.try_convert(input) || # ruby 3.1+
1276
- input.respond_to?(:to_int) && Integer(input.to_int) ||
1420
+ Integer.try_convert(input) ||
1277
1421
  String.try_convert(input) ||
1278
1422
  input
1279
1423
  end
@@ -1305,6 +1449,12 @@ module Net
1305
1449
  range.include?(min) || range.include?(max) || (min..max).cover?(range)
1306
1450
  end
1307
1451
 
1452
+ def modifying!
1453
+ if frozen?
1454
+ raise FrozenError, "can't modify frozen #{self.class}: %p" % [self]
1455
+ end
1456
+ end
1457
+
1308
1458
  def tuples_add(tuples) tuples.each do tuple_add _1 end; self end
1309
1459
  def tuples_subtract(tuples) tuples.each do tuple_subtract _1 end; self end
1310
1460
 
@@ -1319,10 +1469,11 @@ module Net
1319
1469
  # ---------??===lower==|--|==|----|===upper===|-- join until upper
1320
1470
  # ---------??===lower==|--|==|--|=====upper===|-- join to upper
1321
1471
  def tuple_add(tuple)
1472
+ modifying!
1322
1473
  min, max = tuple
1323
1474
  lower, lower_idx = tuple_gte_with_index(min - 1)
1324
- if lower.nil? then tuples << tuple
1325
- elsif (max + 1) < lower.first then tuples.insert(lower_idx, tuple)
1475
+ if lower.nil? then tuples << [min, max]
1476
+ elsif (max + 1) < lower.first then tuples.insert(lower_idx, [min, max])
1326
1477
  else tuple_coalesce(lower, lower_idx, min, max)
1327
1478
  end
1328
1479
  end
@@ -1355,6 +1506,7 @@ module Net
1355
1506
  # -------??=====lower====|--|====|---|====upper====|-- 7. delete until
1356
1507
  # -------??=====lower====|--|====|--|=====upper====|-- 8. delete and trim
1357
1508
  def tuple_subtract(tuple)
1509
+ modifying!
1358
1510
  min, max = tuple
1359
1511
  lower, idx = tuple_gte_with_index(min)
1360
1512
  if lower.nil? then nil # case 1.
@@ -1395,12 +1547,11 @@ module Net
1395
1547
  end
1396
1548
 
1397
1549
  def nz_number(num)
1398
- case num
1399
- when Integer, /\A[1-9]\d*\z/ then num = Integer(num)
1400
- else raise DataFormatError, "%p is not a valid nz-number" % [num]
1401
- end
1402
- NumValidator.ensure_nz_number(num)
1403
- num
1550
+ String === num && !/\A[1-9]\d*\z/.match?(num) and
1551
+ raise DataFormatError, "%p is not a valid nz-number" % [num]
1552
+ NumValidator.ensure_nz_number Integer num
1553
+ rescue TypeError # To catch errors from Integer()
1554
+ raise DataFormatError, $!.message
1404
1555
  end
1405
1556
 
1406
1557
  # intentionally defined after the class implementation
@@ -4,7 +4,7 @@ module Net
4
4
  class IMAP
5
5
  module StringPrep
6
6
 
7
- # Defined in RFC3491[https://tools.ietf.org/html/rfc3491], the +nameprep+
7
+ # Defined in RFC3491[https://www.rfc-editor.org/rfc/rfc3491], the +nameprep+
8
8
  # profile of "Stringprep" is:
9
9
  # >>>
10
10
  # used by the IDNA protocol for preparing domain names; it is not
@@ -4,11 +4,11 @@ module Net
4
4
  class IMAP
5
5
  module StringPrep
6
6
 
7
- # Defined in RFC-4505[https://tools.ietf.org/html/rfc4505] §3, The +trace+
7
+ # Defined in RFC-4505[https://www.rfc-editor.org/rfc/rfc4505] §3, The +trace+
8
8
  # profile of \StringPrep is used by the +ANONYMOUS+ \SASL mechanism.
9
9
  module Trace
10
10
 
11
- # Defined in RFC-4505[https://tools.ietf.org/html/rfc4505] §3.
11
+ # Defined in RFC-4505[https://www.rfc-editor.org/rfc/rfc4505] §3.
12
12
  STRINGPREP_PROFILE = "trace"
13
13
 
14
14
  # >>>
@@ -23,7 +23,7 @@ module Net
23
23
  # No Unicode normalization is required by this profile.
24
24
  NORMALIZATION = nil
25
25
 
26
- # From RFC-4505[https://tools.ietf.org/html/rfc4505] §3, The "trace"
26
+ # From RFC-4505[https://www.rfc-editor.org/rfc/rfc4505] §3, The "trace"
27
27
  # Profile of "Stringprep":
28
28
  # >>>
29
29
  # Characters from the following tables of [StringPrep] are prohibited:
@@ -47,7 +47,7 @@ module Net
47
47
 
48
48
  module_function
49
49
 
50
- # From RFC-4505[https://tools.ietf.org/html/rfc4505] §3, The "trace"
50
+ # From RFC-4505[https://www.rfc-editor.org/rfc/rfc4505] §3, The "trace"
51
51
  # Profile of "Stringprep":
52
52
  # >>>
53
53
  # The character repertoire of this profile is Unicode 3.2 [Unicode].