net-imap 0.5.7 → 0.5.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ee48ac78f129043fd8ccb4f6e0b42535cc0ef3a6acd8ea1067cbd9b512815e49
4
- data.tar.gz: 5bfae3bf0ee2e63c5b61c19d30187fe722adfe8018746e64d583124531de87a8
3
+ metadata.gz: 261794e07175481d35e146b718183fc057a3a54e2fd9958b25918c6bd4178ec4
4
+ data.tar.gz: 2568eb1d284b3f1662d1cf0c1bc79eb65722fa3ba8dc03072b52fbd3716c9294
5
5
  SHA512:
6
- metadata.gz: f90a4500f3c218dd3a51cca84a76c620fa8f8488f2ca73486b538a43e67e21e0b897d23d957f490afe1590fd755c692f2e0dede562460ed2cfcd7f4d9bec3262
7
- data.tar.gz: 22abb3cf5699bb715c33c69b596ead46ff400109925f58d633ecdc82797be9b80fbbd040c02aa7cd9286f3aa9c697e9fc426f1115b9019455b2ac4e6667cc236
6
+ metadata.gz: 357279f77c69c27b78924847216afad6c806abb7bfee5f0c95aab6c397cb10298440a59cb32b9f75a4174e6965d6bfe48a5e847e20d9098845033e8453f73302
7
+ data.tar.gz: e3c29888d787de23a843bfb9ff9194d1fa136763e9d9bcf36800953362f7611c489da1f9150ee2a23baa571125f4f2af2cebe4ee4666447783e355bd9995eb3a
data/Gemfile CHANGED
@@ -14,7 +14,9 @@ gem "rdoc"
14
14
  gem "test-unit"
15
15
  gem "test-unit-ruby-core", git: "https://github.com/ruby/test-unit-ruby-core"
16
16
 
17
+ gem "benchmark", require: false
17
18
  gem "benchmark-driver", require: false
19
+ gem "vernier", require: false, platform: :mri
18
20
 
19
21
  group :test do
20
22
  gem "simplecov", require: false
@@ -28,7 +28,11 @@ module Net
28
28
  end
29
29
  private_class_method :included
30
30
 
31
- def self.safe(...) = Ractor.make_shareable nil.instance_eval(...).freeze
31
+ if defined?(Ractor.make_shareable)
32
+ def self.safe(...) Ractor.make_shareable nil.instance_eval(...).freeze end
33
+ else
34
+ def self.safe(...) nil.instance_eval(...).freeze end
35
+ end
32
36
  private_class_method :safe
33
37
 
34
38
  Types = Hash.new do |h, type| type => Proc | nil; safe{type} end
@@ -173,7 +173,7 @@ module Net
173
173
  SUBSCRIBED = :Subscribed
174
174
 
175
175
  # The mailbox is a remote mailbox.
176
- REMOTE = :Remove
176
+ REMOTE = :Remote
177
177
 
178
178
  # Alias for NO_INFERIORS, to match the \IMAP spelling.
179
179
  NOINFERIORS = NO_INFERIORS
@@ -6,7 +6,6 @@ module Net
6
6
  autoload :FetchData, "#{__dir__}/fetch_data"
7
7
  autoload :UIDFetchData, "#{__dir__}/fetch_data"
8
8
  autoload :SearchResult, "#{__dir__}/search_result"
9
- autoload :SequenceSet, "#{__dir__}/sequence_set"
10
9
  autoload :UIDPlusData, "#{__dir__}/uidplus_data"
11
10
  autoload :AppendUIDData, "#{__dir__}/uidplus_data"
12
11
  autoload :CopyUIDData, "#{__dir__}/uidplus_data"
@@ -18,21 +18,9 @@ module Net
18
18
  #
19
19
  # == Creating sequence sets
20
20
  #
21
- # SequenceSet.new with no arguments creates an empty sequence set. Note
22
- # that an empty sequence set is invalid in the \IMAP grammar.
23
- #
24
- # set = Net::IMAP::SequenceSet.new
25
- # set.empty? #=> true
26
- # set.valid? #=> false
27
- # set.valid_string #!> raises DataFormatError
28
- # set << 1..10
29
- # set.empty? #=> false
30
- # set.valid? #=> true
31
- # set.valid_string #=> "1:10"
32
- #
33
21
  # SequenceSet.new may receive a single optional argument: a non-zero 32 bit
34
22
  # unsigned integer, a range, a <tt>sequence-set</tt> formatted string,
35
- # another sequence set, a Set (containing only numbers or <tt>*</tt>), or an
23
+ # another SequenceSet, a Set (containing only numbers or <tt>*</tt>), or an
36
24
  # Array containing any of these (array inputs may be nested).
37
25
  #
38
26
  # set = Net::IMAP::SequenceSet.new(1)
@@ -48,30 +36,114 @@ module Net
48
36
  # set = Net::IMAP::SequenceSet.new(1, 2, 3..7, 5, 6..10, 2048, 1024)
49
37
  # set.valid_string #=> "1:10,55,1024:2048"
50
38
  #
51
- # Use ::[] with one or more arguments to create a frozen SequenceSet. An
52
- # invalid (empty) set cannot be created with ::[].
39
+ # SequenceSet.new with no arguments creates an empty sequence set. Note
40
+ # that an empty sequence set is invalid in the \IMAP grammar.
41
+ #
42
+ # set = Net::IMAP::SequenceSet.new
43
+ # set.empty? #=> true
44
+ # set.valid? #=> false
45
+ # set.valid_string #!> raises DataFormatError
46
+ # set << 1..10
47
+ # set.empty? #=> false
48
+ # set.valid? #=> true
49
+ # set.valid_string #=> "1:10"
53
50
  #
51
+ # Using SequenceSet.new with another SequenceSet input behaves the same as
52
+ # calling #dup on the other set. The input's #string will be preserved.
53
+ #
54
+ # input = Net::IMAP::SequenceSet.new("1,2,3:7,5,6:10,2048,1024")
55
+ # copy = Net::IMAP::SequenceSet.new(input)
56
+ # input.valid_string #=> "1,2,3:7,5,6:10,2048,1024"
57
+ # copy.valid_string #=> "1,2,3:7,5,6:10,2048,1024"
58
+ # copy2 = input.dup # same as calling new with a SequenceSet input
59
+ # copy == input #=> true, same set membership
60
+ # copy.eql? input #=> true, same string value
61
+ # copy.equal? input #=> false, different objects
62
+ #
63
+ # copy.normalize!
64
+ # copy.valid_string #=> "1:10,1024,2048"
65
+ # copy == input #=> true, same set membership
66
+ # copy.eql? input #=> false, different string value
67
+ #
68
+ # copy << 999
69
+ # copy.valid_string #=> "1:10,999,1024,2048"
70
+ # copy == input #=> false, different set membership
71
+ # copy.eql? input #=> false, different string value
72
+ #
73
+ # Use Net::IMAP::SequenceSet() to coerce a single (optional) input.
74
+ # A SequenceSet input is returned without duplication, even when frozen.
75
+ #
76
+ # set = Net::IMAP::SequenceSet()
77
+ # set.string #=> nil
78
+ # set.frozen? #=> false
79
+ #
80
+ # # String order is preserved
81
+ # set = Net::IMAP::SequenceSet("1,2,3:7,5,6:10,2048,1024")
82
+ # set.valid_string #=> "1,2,3:7,5,6:10,2048,1024"
83
+ # set.frozen? #=> false
84
+ #
85
+ # # Other inputs are normalized
86
+ # set = Net::IMAP::SequenceSet([1, 2, [3..7, 5], 6..10, 2048, 1024])
87
+ # set.valid_string #=> "1:10,1024,2048"
88
+ # set.frozen? #=> false
89
+ #
90
+ # unfrozen = set
91
+ # frozen = set.dup.freeze
92
+ # unfrozen.equal? Net::IMAP::SequenceSet(unfrozen) #=> true
93
+ # frozen.equal? Net::IMAP::SequenceSet(frozen) #=> true
94
+ #
95
+ # Use ::[] to coerce one or more arguments into a valid frozen SequenceSet.
96
+ # A valid frozen SequenceSet is returned directly, without allocating a new
97
+ # object. ::[] will not create an invalid (empty) set.
98
+ #
99
+ # Net::IMAP::SequenceSet[] #!> raises ArgumentError
100
+ # Net::IMAP::SequenceSet[nil] #!> raises DataFormatError
101
+ # Net::IMAP::SequenceSet[""] #!> raises DataFormatError
102
+ #
103
+ # # String order is preserved
54
104
  # set = Net::IMAP::SequenceSet["1,2,3:7,5,6:10,2048,1024"]
55
105
  # set.valid_string #=> "1,2,3:7,5,6:10,2048,1024"
106
+ # set.frozen? #=> true
107
+ #
108
+ # # Other inputs are normalized
56
109
  # set = Net::IMAP::SequenceSet[1, 2, [3..7, 5], 6..10, 2048, 1024]
57
- # set.valid_string #=> "1:10,55,1024:2048"
110
+ # set.valid_string #=> "1:10,1024,2048"
111
+ # set.frozen? #=> true
112
+ #
113
+ # frozen = set
114
+ # unfrozen = set.dup
115
+ # frozen.equal? Net::IMAP::SequenceSet[frozen] #=> true
116
+ # unfrozen.equal? Net::IMAP::SequenceSet[unfrozen] #=> false
117
+ #
118
+ # Objects which respond to +to_sequence_set+ (such as SearchResult and
119
+ # ThreadMember) can be coerced to a SequenceSet with ::new, ::try_convert,
120
+ # ::[], or Net::IMAP::SequenceSet.
121
+ #
122
+ # search = imap.uid_search(["SUBJECT", "hello", "NOT", "SEEN"])
123
+ # seqset = Net::IMAP::SequenceSet(search) - already_fetched
124
+ # fetch = imap.uid_fetch(seqset, "FAST")
58
125
  #
59
126
  # == Ordered and Normalized sets
60
127
  #
61
128
  # Sometimes the order of the set's members is significant, such as with the
62
129
  # +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.
130
+ # sequence set is created from a single string (such as by the parser), that
131
+ # #string representation is preserved. Assigning a string with #string= or
132
+ # #replace will also preserve that string. Use #each_entry, #entries, or
133
+ # #each_ordered_number to enumerate the entries in their #string order.
134
+ # Hash equality (using #eql?) is based on the string representation.
65
135
  #
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.
136
+ # Internally, SequenceSet uses a normalized uint32 set representation which
137
+ # sorts and de-duplicates all numbers and coalesces adjacent or overlapping
138
+ # entries. Many methods use this sorted set representation for <tt>O(lg
139
+ # n)</tt> searches. Use #each_element, #elements, #each_range, #ranges,
140
+ # #each_number, or #numbers to enumerate the set in sorted order. Basic
141
+ # object equality (using #==) is based on set membership, without regard to
142
+ # #entry order or #string normalization.
71
143
  #
72
- # Most modification methods convert #string to its normalized form. To
73
- # preserve #string order while modifying a set, use #append, #string=, or
74
- # #replace.
144
+ # Most modification methods reset #string to its #normalized form, so that
145
+ # #entries and #elements are identical. Use #append to preserve #entries
146
+ # order while modifying a set.
75
147
  #
76
148
  # == Using <tt>*</tt>
77
149
  #
@@ -108,11 +180,15 @@ module Net
108
180
  # When a set includes <tt>*</tt>, some methods may have surprising behavior.
109
181
  #
110
182
  # For example, #complement treats <tt>*</tt> as its own number. This way,
111
- # the #intersection of a set and its #complement will always be empty.
112
- # This is not how an \IMAP server interprets the set: it will convert
113
- # <tt>*</tt> to either the number of messages in the mailbox or +UIDNEXT+,
114
- # as appropriate. And there _will_ be overlap between a set and its
115
- # complement after #limit is applied to each:
183
+ # the #intersection of a set and its #complement will always be empty. And
184
+ # <tt>*</tt> is sorted as greater than any other number in the set. This is
185
+ # not how an \IMAP server interprets the set: it will convert <tt>*</tt> to
186
+ # the number of messages in the mailbox, the +UID+ of the last message in
187
+ # the mailbox, or +UIDNEXT+, as appropriate. Several methods have an
188
+ # argument for how <tt>*</tt> should be interpreted.
189
+ #
190
+ # But, for example, this means that there may be overlap between a set and
191
+ # its complement after #limit is applied to each:
116
192
  #
117
193
  # ~Net::IMAP::SequenceSet["*"] == Net::IMAP::SequenceSet[1..(2**32-1)]
118
194
  # ~Net::IMAP::SequenceSet[1..5] == Net::IMAP::SequenceSet["6:*"]
@@ -149,6 +225,7 @@ module Net
149
225
  # * ::new: Creates a new mutable sequence set, which may be empty (invalid).
150
226
  # * ::try_convert: Calls +to_sequence_set+ on an object and verifies that
151
227
  # the result is a SequenceSet.
228
+ # * Net::IMAP::SequenceSet(): Coerce an input using ::try_convert or ::new.
152
229
  # * ::empty: Returns a frozen empty (invalid) SequenceSet.
153
230
  # * ::full: Returns a frozen SequenceSet containing every possible number.
154
231
  #
@@ -174,14 +251,13 @@ module Net
174
251
  #
175
252
  # <i>Set membership:</i>
176
253
  # - #include? (aliased as #member?):
177
- # Returns whether a given element (nz-number, range, or <tt>*</tt>) is
178
- # contained by the set.
254
+ # Returns whether a given element is contained by the set.
179
255
  # - #include_star?: Returns whether the set contains <tt>*</tt>.
180
256
  #
181
257
  # <i>Minimum and maximum value elements:</i>
182
- # - #min: Returns the minimum number in the set.
183
- # - #max: Returns the maximum number in the set.
184
- # - #minmax: Returns the minimum and maximum numbers in the set.
258
+ # - #min: Returns one or more of the lowest numbers in the set.
259
+ # - #max: Returns one or more of the highest numbers in the set.
260
+ # - #minmax: Returns the lowest and highest numbers in the set.
185
261
  #
186
262
  # <i>Accessing value by offset in sorted set:</i>
187
263
  # - #[] (aliased as #slice): Returns the number or consecutive subset at a
@@ -248,6 +324,10 @@ module Net
248
324
  # +self+ and the other set except those common to both.
249
325
  # - #~ (aliased as #complement): Returns a new set containing all members
250
326
  # that are not in +self+
327
+ # - #above: Return a copy of +self+ which only contains numbers above a
328
+ # given number.
329
+ # - #below: Return a copy of +self+ which only contains numbers below a
330
+ # given value.
251
331
  # - #limit: Returns a copy of +self+ which has replaced <tt>*</tt> with a
252
332
  # given maximum value and removed all members over that maximum.
253
333
  #
@@ -316,6 +396,23 @@ module Net
316
396
  STARS = [:*, ?*, -1].freeze
317
397
  private_constant :STARS
318
398
 
399
+ INSPECT_MAX_LEN = 512
400
+ INSPECT_TRUNCATE_LEN = 16
401
+ private_constant :INSPECT_MAX_LEN, :INSPECT_TRUNCATE_LEN
402
+
403
+ # /(,\d+){100}\z/ is shockingly slow on huge strings.
404
+ # /(,\d{0,10}){100}\z/ is ok, but ironically, Regexp.linear_time? is false.
405
+ #
406
+ # This unrolls all nested quantifiers. It's much harder to read, but it's
407
+ # also the fastest out of all the versions I tested.
408
+ nz_uint32 = /[1-9](?:\d(?:\d(?:\d(?:\d(?:\d(?:\d(?:\d(?:\d(?:\d)?)?)?)?)?)?)?)?)?/
409
+ num_or_star = /#{nz_uint32}|\*/
410
+ entry = /#{num_or_star}(?::#{num_or_star})?/
411
+ entries = ([entry] * INSPECT_TRUNCATE_LEN).join(",")
412
+ INSPECT_ABRIDGED_HEAD_RE = /\A#{entries},/
413
+ INSPECT_ABRIDGED_TAIL_RE = /,#{entries}\z/
414
+ private_constant :INSPECT_ABRIDGED_HEAD_RE, :INSPECT_ABRIDGED_TAIL_RE
415
+
319
416
  class << self
320
417
 
321
418
  # :call-seq:
@@ -329,13 +426,12 @@ module Net
329
426
  # An empty SequenceSet is invalid and will raise a DataFormatError.
330
427
  #
331
428
  # Use ::new to create a mutable or empty SequenceSet.
429
+ #
430
+ # Related: ::new, Net::IMAP::SequenceSet(), ::try_convert
332
431
  def [](first, *rest)
333
432
  if rest.empty?
334
- if first.is_a?(SequenceSet) && first.frozen? && first.valid?
335
- first
336
- else
337
- new(first).validate.freeze
338
- end
433
+ set = try_convert(first)&.validate
434
+ set&.frozen? ? set : (set&.dup || new(first).validate).freeze
339
435
  else
340
436
  new(first).merge(*rest).validate.freeze
341
437
  end
@@ -350,6 +446,8 @@ module Net
350
446
  #
351
447
  # If +obj.to_sequence_set+ doesn't return a SequenceSet, an exception is
352
448
  # raised.
449
+ #
450
+ # Related: Net::IMAP::SequenceSet(), ::new, ::[]
353
451
  def try_convert(obj)
354
452
  return obj if obj.is_a?(SequenceSet)
355
453
  return nil unless obj.respond_to?(:to_sequence_set)
@@ -368,23 +466,91 @@ module Net
368
466
  end
369
467
 
370
468
  # Create a new SequenceSet object from +input+, which may be another
371
- # SequenceSet, an IMAP formatted +sequence-set+ string, a number, a
372
- # range, <tt>:*</tt>, or an enumerable of these.
373
- #
374
- # Use ::[] to create a frozen (non-empty) SequenceSet.
469
+ # SequenceSet, an IMAP formatted +sequence-set+ string, a non-zero 32 bit
470
+ # unsigned integer, a range, <tt>:*</tt>, a Set of numbers or <tt>*</tt>,
471
+ # an object that responds to +to_sequence_set+ (such as SearchResult) or
472
+ # an Array of these (array inputs may be nested).
473
+ #
474
+ # set = Net::IMAP::SequenceSet.new(1)
475
+ # set.valid_string #=> "1"
476
+ # set = Net::IMAP::SequenceSet.new(1..100)
477
+ # set.valid_string #=> "1:100"
478
+ # set = Net::IMAP::SequenceSet.new(1...100)
479
+ # set.valid_string #=> "1:99"
480
+ # set = Net::IMAP::SequenceSet.new([1, 2, 5..])
481
+ # set.valid_string #=> "1:2,5:*"
482
+ # set = Net::IMAP::SequenceSet.new("1,2,3:7,5,6:10,2048,1024")
483
+ # set.valid_string #=> "1,2,3:7,5,6:10,2048,1024"
484
+ # set = Net::IMAP::SequenceSet.new(1, 2, 3..7, 5, 6..10, 2048, 1024)
485
+ # set.valid_string #=> "1:10,1024,2048"
486
+ #
487
+ # With no arguments (or +nil+) creates an empty sequence set. Note that
488
+ # an empty sequence set is invalid in the \IMAP grammar.
489
+ #
490
+ # set = Net::IMAP::SequenceSet.new
491
+ # set.empty? #=> true
492
+ # set.valid? #=> false
493
+ # set.valid_string #!> raises DataFormatError
494
+ # set << 1..10
495
+ # set.empty? #=> false
496
+ # set.valid? #=> true
497
+ # set.valid_string #=> "1:10"
498
+ #
499
+ # When +input+ is a SequenceSet, ::new behaves the same as calling #dup on
500
+ # that other set. The input's #string will be preserved.
501
+ #
502
+ # input = Net::IMAP::SequenceSet.new("1,2,3:7,5,6:10,2048,1024")
503
+ # copy = Net::IMAP::SequenceSet.new(input)
504
+ # input.valid_string #=> "1,2,3:7,5,6:10,2048,1024"
505
+ # copy.valid_string #=> "1,2,3:7,5,6:10,2048,1024"
506
+ # copy2 = input.dup # same as calling new with a SequenceSet input
507
+ # copy == input #=> true, same set membership
508
+ # copy.eql? input #=> true, same string value
509
+ # copy.equal? input #=> false, different objects
510
+ #
511
+ # copy.normalize!
512
+ # copy.valid_string #=> "1:10,1024,2048"
513
+ # copy == input #=> true, same set membership
514
+ # copy.eql? input #=> false, different string value
515
+ #
516
+ # copy << 999
517
+ # copy.valid_string #=> "1:10,999,1024,2048"
518
+ # copy == input #=> false, different set membership
519
+ # copy.eql? input #=> false, different string value
520
+ #
521
+ # === Alternative set creation methods
522
+ #
523
+ # * ::[] returns a frozen validated (non-empty) SequenceSet, without
524
+ # allocating a new object when the input is already a valid frozen
525
+ # SequenceSet.
526
+ # * Net::IMAP::SequenceSet() coerces an input to SequenceSet, without
527
+ # allocating a new object when the input is already a SequenceSet.
528
+ # * ::try_convert calls +to_sequence_set+ on inputs that support it and
529
+ # returns +nil+ for inputs that don't.
530
+ # * ::empty and ::full both return frozen singleton sets which can be
531
+ # combined with set operations (#|, #&, #^, #-, etc) to make new sets.
532
+ #
533
+ # See SequenceSet@Creating+sequence+sets.
375
534
  def initialize(input = nil) input ? replace(input) : clear end
376
535
 
377
536
  # Removes all elements and returns self.
378
- def clear; @tuples, @string = [], nil; self end
537
+ def clear
538
+ modifying! # redundant check, to normalize the error message for JRuby
539
+ @tuples, @string = [], nil
540
+ self
541
+ end
379
542
 
380
543
  # Replace the contents of the set with the contents of +other+ and returns
381
544
  # +self+.
382
545
  #
383
- # +other+ may be another SequenceSet, or it may be an IMAP +sequence-set+
384
- # string, a number, a range, <tt>*</tt>, or an enumerable of these.
546
+ # +other+ may be another SequenceSet or any other object that would be
547
+ # accepted by ::new.
385
548
  def replace(other)
386
549
  case other
387
- when SequenceSet then initialize_dup(other)
550
+ when SequenceSet then
551
+ modifying! # short circuit before doing any work
552
+ @tuples = other.deep_copy_tuples
553
+ @string = other.instance_variable_get(:@string)
388
554
  when String then self.string = other
389
555
  else clear; merge other
390
556
  end
@@ -413,36 +579,40 @@ module Net
413
579
  # If the set was created from a single string, it is not normalized. If
414
580
  # the set is updated the string will be normalized.
415
581
  #
416
- # Related: #valid_string, #normalized_string, #to_s
582
+ # Related: #valid_string, #normalized_string, #to_s, #inspect
417
583
  def string; @string ||= normalized_string if valid? end
418
584
 
419
585
  # Returns an array with #normalized_string when valid and an empty array
420
586
  # otherwise.
421
587
  def deconstruct; valid? ? [normalized_string] : [] end
422
588
 
423
- # Assigns a new string to #string and resets #elements to match. It
424
- # cannot be set to an empty string—assign +nil+ or use #clear instead.
425
- # The string is validated but not normalized.
589
+ # Assigns a new string to #string and resets #elements to match.
590
+ # Assigning +nil+ or an empty string are equivalent to calling #clear.
426
591
  #
427
- # Use #add or #merge to add a string to an existing set.
592
+ # Non-empty strings are validated but not normalized.
593
+ #
594
+ # Use #add, #merge, or #append to add a string to an existing set.
428
595
  #
429
596
  # Related: #replace, #clear
430
- def string=(str)
431
- if str.nil?
597
+ def string=(input)
598
+ if input.nil?
432
599
  clear
433
- else
434
- str = String.try_convert(str) or raise ArgumentError, "not a string"
600
+ elsif (str = String.try_convert(input))
601
+ modifying! # short-circuit before parsing the string
435
602
  tuples = str_to_tuples str
436
603
  @tuples, @string = [], -str
437
604
  tuples_add tuples
605
+ else
606
+ raise ArgumentError, "expected a string or nil, got #{input.class}"
438
607
  end
608
+ str
439
609
  end
440
610
 
441
611
  # Returns the \IMAP +sequence-set+ string representation, or an empty
442
612
  # string when the set is empty. Note that an empty set is invalid in the
443
613
  # \IMAP syntax.
444
614
  #
445
- # Related: #valid_string, #normalized_string, #to_s
615
+ # Related: #string, #valid_string, #normalized_string, #inspect
446
616
  def to_s; string || "" end
447
617
 
448
618
  # Freezes and returns the set. A frozen SequenceSet is Ractor-safe.
@@ -495,8 +665,9 @@ module Net
495
665
 
496
666
  # :call-seq: self === other -> true | false | nil
497
667
  #
498
- # Returns whether +other+ is contained within the set. Returns +nil+ if a
499
- # StandardError is raised while converting +other+ to a comparable type.
668
+ # Returns whether +other+ is contained within the set. +other+ may be any
669
+ # object that would be accepted by ::new. Returns +nil+ if StandardError
670
+ # is raised while converting +other+ to a comparable type.
500
671
  #
501
672
  # Related: #cover?, #include?, #include_star?
502
673
  def ===(other)
@@ -510,12 +681,12 @@ module Net
510
681
  # Returns whether +other+ is contained within the set. +other+ may be any
511
682
  # object that would be accepted by ::new.
512
683
  #
513
- # Related: #===, #include?, #include_star?
684
+ # Related: #===, #include?, #include_star?, #intersect?
514
685
  def cover?(other) input_to_tuples(other).none? { !include_tuple?(_1) } end
515
686
 
516
687
  # Returns +true+ when a given number or range is in +self+, and +false+
517
- # otherwise. Returns +false+ unless +number+ is an Integer, Range, or
518
- # <tt>*</tt>.
688
+ # otherwise. Returns +nil+ when +number+ isn't a valid SequenceSet
689
+ # element (Integer, Range, <tt>*</tt>, +sequence-set+ string).
519
690
  #
520
691
  # set = Net::IMAP::SequenceSet["5:10,100,111:115"]
521
692
  # set.include? 1 #=> false
@@ -523,8 +694,8 @@ module Net
523
694
  # set.include? 11..20 #=> false
524
695
  # set.include? 100 #=> true
525
696
  # set.include? 6 #=> true, covered by "5:10"
526
- # set.include? 4..9 #=> true, covered by "5:10"
527
- # set.include? "4:9" #=> true, strings are parsed
697
+ # set.include? 6..9 #=> true, covered by "5:10"
698
+ # set.include? "6:9" #=> true, strings are parsed
528
699
  # set.include? 4..9 #=> false, intersection is not sufficient
529
700
  # set.include? "*" #=> false, use #limit to re-interpret "*"
530
701
  # set.include? -1 #=> false, -1 is interpreted as "*"
@@ -533,11 +704,14 @@ module Net
533
704
  # set.include? :* #=> true
534
705
  # set.include? "*" #=> true
535
706
  # set.include? -1 #=> true
536
- # set.include? 200.. #=> true
537
- # set.include? 100.. #=> false
707
+ # set.include?(200..) #=> true
708
+ # set.include?(100..) #=> false
538
709
  #
539
- # Related: #include_star?, #cover?, #===
540
- def include?(element) include_tuple? input_to_tuple element end
710
+ # Related: #include_star?, #cover?, #===, #intersect?
711
+ def include?(element)
712
+ tuple = input_to_tuple element rescue nil
713
+ !!include_tuple?(tuple) if tuple
714
+ end
541
715
 
542
716
  alias member? include?
543
717
 
@@ -550,7 +724,7 @@ module Net
550
724
  # Net::IMAP::SequenceSet["5:10"].intersect? "7,9,11" #=> true
551
725
  # Net::IMAP::SequenceSet["5:10"].intersect? "11:33" #=> false
552
726
  #
553
- # Related: #intersection, #disjoint?
727
+ # Related: #intersection, #disjoint?, #cover?, #include?
554
728
  def intersect?(other)
555
729
  valid? && input_to_tuples(other).any? { intersect_tuple? _1 }
556
730
  end
@@ -567,26 +741,53 @@ module Net
567
741
  empty? || input_to_tuples(other).none? { intersect_tuple? _1 }
568
742
  end
569
743
 
570
- # :call-seq: max(star: :*) => integer or star or nil
744
+ # :call-seq:
745
+ # max(star: :*) => integer or star or nil
746
+ # max(count) => SequenceSet
571
747
  #
572
748
  # Returns the maximum value in +self+, +star+ when the set includes
573
749
  # <tt>*</tt>, or +nil+ when the set is empty.
574
- def max(star: :*)
575
- (val = @tuples.last&.last) && val == STAR_INT ? star : val
750
+ #
751
+ # When +count+ is given, a new SequenceSet is returned, containing only
752
+ # the last +count+ numbers. An empty SequenceSet is returned when +self+
753
+ # is empty. (+star+ is ignored when +count+ is given.)
754
+ #
755
+ # Related: #min, #minmax, #slice
756
+ def max(count = nil, star: :*)
757
+ if count
758
+ slice(-[count, size].min..) || remain_frozen_empty
759
+ elsif (val = @tuples.last&.last)
760
+ val == STAR_INT ? star : val
761
+ end
576
762
  end
577
763
 
578
- # :call-seq: min(star: :*) => integer or star or nil
764
+ # :call-seq:
765
+ # min(star: :*) => integer or star or nil
766
+ # min(count) => SequenceSet
579
767
  #
580
768
  # Returns the minimum value in +self+, +star+ when the only value in the
581
769
  # set is <tt>*</tt>, or +nil+ when the set is empty.
582
- def min(star: :*)
583
- (val = @tuples.first&.first) && val == STAR_INT ? star : val
770
+ #
771
+ # When +count+ is given, a new SequenceSet is returned, containing only
772
+ # the first +count+ numbers. An empty SequenceSet is returned when +self+
773
+ # is empty. (+star+ is ignored when +count+ is given.)
774
+ #
775
+ # Related: #max, #minmax, #slice
776
+ def min(count = nil, star: :*)
777
+ if count
778
+ slice(0...count) || remain_frozen_empty
779
+ elsif (val = @tuples.first&.first)
780
+ val != STAR_INT ? val : star
781
+ end
584
782
  end
585
783
 
586
- # :call-seq: minmax(star: :*) => nil or [integer, integer or star]
784
+ # :call-seq: minmax(star: :*) => [min, max] or nil
587
785
  #
588
786
  # Returns a 2-element array containing the minimum and maximum numbers in
589
- # +self+, or +nil+ when the set is empty.
787
+ # +self+, or +nil+ when the set is empty. +star+ is handled the same way
788
+ # as by #min and #max.
789
+ #
790
+ # Related: #min, #max
590
791
  def minmax(star: :*); [min(star: star), max(star: star)] unless empty? end
591
792
 
592
793
  # Returns false when the set is empty.
@@ -606,14 +807,19 @@ module Net
606
807
  # Returns a new sequence set that has every number in the +other+ object
607
808
  # added.
608
809
  #
609
- # +other+ may be any object that would be accepted by ::new: a non-zero 32
610
- # bit unsigned integer, range, <tt>sequence-set</tt> formatted string,
611
- # another sequence set, or an enumerable containing any of these.
810
+ # +other+ may be any object that would be accepted by ::new.
612
811
  #
613
812
  # Net::IMAP::SequenceSet["1:5"] | 2 | [4..6, 99]
614
813
  # #=> Net::IMAP::SequenceSet["1:6,99"]
615
814
  #
616
- # Related: #add, #merge
815
+ # Related: #add, #merge, #&, #-, #^, #~
816
+ #
817
+ # ==== Set identities
818
+ #
819
+ # <tt>lhs | rhs</tt> is equivalent to:
820
+ # * <tt>rhs | lhs</tt> (commutative)
821
+ # * <tt>~(~lhs & ~rhs)</tt> (De Morgan's Law)
822
+ # * <tt>(lhs & rhs) ^ (lhs ^ rhs)</tt>
617
823
  def |(other) remain_frozen dup.merge other end
618
824
  alias :+ :|
619
825
  alias union :|
@@ -625,14 +831,22 @@ module Net
625
831
  # Returns a new sequence set built by duplicating this set and removing
626
832
  # every number that appears in +other+.
627
833
  #
628
- # +other+ may be any object that would be accepted by ::new: a non-zero 32
629
- # bit unsigned integer, range, <tt>sequence-set</tt> formatted string,
630
- # another sequence set, or an enumerable containing any of these.
834
+ # +other+ may be any object that would be accepted by ::new.
631
835
  #
632
836
  # Net::IMAP::SequenceSet[1..5] - 2 - 4 - 6
633
837
  # #=> Net::IMAP::SequenceSet["1,3,5"]
634
838
  #
635
- # Related: #subtract
839
+ # Related: #subtract, #|, #&, #^, #~
840
+ #
841
+ # ==== Set identities
842
+ #
843
+ # <tt>lhs - rhs</tt> is equivalent to:
844
+ # * <tt>~rhs - ~lhs</tt>
845
+ # * <tt>lhs & ~rhs</tt>
846
+ # * <tt>~(~lhs | rhs)</tt>
847
+ # * <tt>lhs & (lhs ^ rhs)</tt>
848
+ # * <tt>lhs ^ (lhs & rhs)</tt>
849
+ # * <tt>rhs ^ (lhs | rhs)</tt>
636
850
  def -(other) remain_frozen dup.subtract other end
637
851
  alias difference :-
638
852
 
@@ -643,14 +857,22 @@ module Net
643
857
  # Returns a new sequence set containing only the numbers common to this
644
858
  # set and +other+.
645
859
  #
646
- # +other+ may be any object that would be accepted by ::new: a non-zero 32
647
- # bit unsigned integer, range, <tt>sequence-set</tt> formatted string,
648
- # another sequence set, or an enumerable containing any of these.
860
+ # +other+ may be any object that would be accepted by ::new.
649
861
  #
650
862
  # Net::IMAP::SequenceSet[1..5] & [2, 4, 6]
651
863
  # #=> Net::IMAP::SequenceSet["2,4"]
652
864
  #
653
- # <tt>(seqset & other)</tt> is equivalent to <tt>(seqset - ~other)</tt>.
865
+ # Related: #intersect?, #|, #-, #^, #~
866
+ #
867
+ # ==== Set identities
868
+ #
869
+ # <tt>lhs & rhs</tt> is equivalent to:
870
+ # * <tt>rhs & lhs</tt> (commutative)
871
+ # * <tt>~(~lhs | ~rhs)</tt> (De Morgan's Law)
872
+ # * <tt>lhs - ~rhs</tt>
873
+ # * <tt>lhs - (lhs - rhs)</tt>
874
+ # * <tt>lhs - (lhs ^ rhs)</tt>
875
+ # * <tt>lhs ^ (lhs - rhs)</tt>
654
876
  def &(other)
655
877
  remain_frozen dup.subtract SequenceSet.new(other).complement!
656
878
  end
@@ -663,16 +885,22 @@ module Net
663
885
  # Returns a new sequence set containing numbers that are exclusive between
664
886
  # this set and +other+.
665
887
  #
666
- # +other+ may be any object that would be accepted by ::new: a non-zero 32
667
- # bit unsigned integer, range, <tt>sequence-set</tt> formatted string,
668
- # another sequence set, or an enumerable containing any of these.
888
+ # +other+ may be any object that would be accepted by ::new.
669
889
  #
670
890
  # Net::IMAP::SequenceSet[1..5] ^ [2, 4, 6]
671
891
  # #=> Net::IMAP::SequenceSet["1,3,5:6"]
672
892
  #
673
- # <tt>(seqset ^ other)</tt> is equivalent to <tt>((seqset | other) -
674
- # (seqset & other))</tt>.
675
- def ^(other) remain_frozen (self | other).subtract(self & other) end
893
+ # Related: #|, #&, #-, #~
894
+ #
895
+ # ==== Set identities
896
+ #
897
+ # <tt>lhs ^ rhs</tt> is equivalent to:
898
+ # * <tt>rhs ^ lhs</tt> (commutative)
899
+ # * <tt>~lhs ^ ~rhs</tt>
900
+ # * <tt>(lhs | rhs) - (lhs & rhs)</tt>
901
+ # * <tt>(lhs - rhs) | (rhs - lhs)</tt>
902
+ # * <tt>(lhs ^ other) ^ (other ^ rhs)</tt>
903
+ def ^(other) remain_frozen (dup | other).subtract(self & other) end
676
904
  alias xor :^
677
905
 
678
906
  # :call-seq:
@@ -689,7 +917,12 @@ module Net
689
917
  # ~Net::IMAP::SequenceSet["6:99,223:*"]
690
918
  # #=> Net::IMAP::SequenceSet["1:5,100:222"]
691
919
  #
692
- # Related: #complement!
920
+ # Related: #complement!, #|, #&, #-, #^
921
+ #
922
+ # ==== Set identities
923
+ #
924
+ # <tt>~set</tt> is equivalent to:
925
+ # * <tt>full - set</tt>, where "full" is Net::IMAP::SequenceSet.full
693
926
  def ~; remain_frozen dup.complement! end
694
927
  alias complement :~
695
928
 
@@ -701,8 +934,12 @@ module Net
701
934
  #
702
935
  # #string will be regenerated. Use #merge to add many elements at once.
703
936
  #
704
- # Related: #add?, #merge, #union
937
+ # Use #append to append new elements to #string. See
938
+ # SequenceSet@Ordered+and+Normalized+sets.
939
+ #
940
+ # Related: #add?, #merge, #union, #append
705
941
  def add(element)
942
+ modifying! # short-circuit before input_to_tuple
706
943
  tuple_add input_to_tuple element
707
944
  normalize!
708
945
  end
@@ -712,8 +949,12 @@ module Net
712
949
  #
713
950
  # Unlike #add, #merge, or #union, the new value is appended to #string.
714
951
  # This may result in a #string which has duplicates or is out-of-order.
952
+ #
953
+ # See SequenceSet@Ordered+and+Normalized+sets.
954
+ #
955
+ # Related: #add, #merge, #union
715
956
  def append(entry)
716
- modifying!
957
+ modifying! # short-circuit before input_to_tuple
717
958
  tuple = input_to_tuple entry
718
959
  entry = tuple_to_str tuple
719
960
  string unless empty? # write @string before tuple_add
@@ -731,6 +972,7 @@ module Net
731
972
  #
732
973
  # Related: #add, #merge, #union, #include?
733
974
  def add?(element)
975
+ modifying! # short-circuit before include?
734
976
  add element unless include? element
735
977
  end
736
978
 
@@ -743,6 +985,7 @@ module Net
743
985
  #
744
986
  # Related: #delete?, #delete_at, #subtract, #difference
745
987
  def delete(element)
988
+ modifying! # short-circuit before input_to_tuple
746
989
  tuple_subtract input_to_tuple element
747
990
  normalize!
748
991
  end
@@ -780,6 +1023,7 @@ module Net
780
1023
  #
781
1024
  # Related: #delete, #delete_at, #subtract, #difference, #disjoint?
782
1025
  def delete?(element)
1026
+ modifying! # short-circuit before input_to_tuple
783
1027
  tuple = input_to_tuple element
784
1028
  if tuple.first == tuple.last
785
1029
  return unless include_tuple? tuple
@@ -820,6 +1064,7 @@ module Net
820
1064
  #
821
1065
  # Related: #slice, #delete_at, #delete, #delete?, #subtract, #difference
822
1066
  def slice!(index, length = nil)
1067
+ modifying! # short-circuit before slice
823
1068
  deleted = slice(index, length) and subtract deleted
824
1069
  deleted
825
1070
  end
@@ -827,14 +1072,13 @@ module Net
827
1072
  # Merges all of the elements that appear in any of the +sets+ into the
828
1073
  # set, and returns +self+.
829
1074
  #
830
- # The +sets+ may be any objects that would be accepted by ::new: non-zero
831
- # 32 bit unsigned integers, ranges, <tt>sequence-set</tt> formatted
832
- # strings, other sequence sets, or enumerables containing any of these.
1075
+ # The +sets+ may be any objects that would be accepted by ::new.
833
1076
  #
834
1077
  # #string will be regenerated after all sets have been merged.
835
1078
  #
836
1079
  # Related: #add, #add?, #union
837
1080
  def merge(*sets)
1081
+ modifying! # short-circuit before input_to_tuples
838
1082
  tuples_add input_to_tuples sets
839
1083
  normalize!
840
1084
  end
@@ -842,9 +1086,7 @@ module Net
842
1086
  # Removes all of the elements that appear in any of the given +sets+ from
843
1087
  # the set, and returns +self+.
844
1088
  #
845
- # The +sets+ may be any objects that would be accepted by ::new: non-zero
846
- # 32 bit unsigned integers, ranges, <tt>sequence-set</tt> formatted
847
- # strings, other sequence sets, or enumerables containing any of these.
1089
+ # The +sets+ may be any objects that would be accepted by ::new.
848
1090
  #
849
1091
  # Related: #difference
850
1092
  def subtract(*sets)
@@ -860,21 +1102,21 @@ module Net
860
1102
  # This is useful when the given order is significant, for example in a
861
1103
  # ESEARCH response to IMAP#sort.
862
1104
  #
1105
+ # See SequenceSet@Ordered+and+Normalized+sets.
1106
+ #
863
1107
  # Related: #each_entry, #elements
864
1108
  def entries; each_entry.to_a end
865
1109
 
866
1110
  # Returns an array of ranges and integers and <tt>:*</tt>.
867
1111
  #
868
1112
  # The returned elements are sorted and coalesced, even when the input
869
- # #string is not. <tt>*</tt> will sort last. See #normalize.
1113
+ # #string is not. <tt>*</tt> will sort last. See #normalize,
1114
+ # SequenceSet@Ordered+and+Normalized+sets.
870
1115
  #
871
1116
  # By itself, <tt>*</tt> translates to <tt>:*</tt>. A range containing
872
1117
  # <tt>*</tt> translates to an endless range. Use #limit to translate both
873
1118
  # cases to a maximum value.
874
1119
  #
875
- # The returned elements will be sorted and coalesced, even when the input
876
- # #string is not. <tt>*</tt> will sort last. See #normalize.
877
- #
878
1120
  # Net::IMAP::SequenceSet["2,5:9,6,*,12:11"].elements
879
1121
  # #=> [2, 5..9, 11..12, :*]
880
1122
  #
@@ -885,15 +1127,13 @@ module Net
885
1127
  # Returns an array of ranges
886
1128
  #
887
1129
  # The returned elements are sorted and coalesced, even when the input
888
- # #string is not. <tt>*</tt> will sort last. See #normalize.
1130
+ # #string is not. <tt>*</tt> will sort last. See #normalize,
1131
+ # SequenceSet@Ordered+and+Normalized+sets.
889
1132
  #
890
1133
  # <tt>*</tt> translates to an endless range. By itself, <tt>*</tt>
891
1134
  # translates to <tt>:*..</tt>. Use #limit to set <tt>*</tt> to a maximum
892
1135
  # value.
893
1136
  #
894
- # The returned ranges will be sorted and coalesced, even when the input
895
- # #string is not. <tt>*</tt> will sort last. See #normalize.
896
- #
897
1137
  # Net::IMAP::SequenceSet["2,5:9,6,*,12:11"].ranges
898
1138
  # #=> [2..2, 5..9, 11..12, :*..]
899
1139
  # Net::IMAP::SequenceSet["123,999:*,456:789"].ranges
@@ -905,7 +1145,7 @@ module Net
905
1145
  # Returns a sorted array of all of the number values in the sequence set.
906
1146
  #
907
1147
  # The returned numbers are sorted and de-duplicated, even when the input
908
- # #string is not. See #normalize.
1148
+ # #string is not. See #normalize, SequenceSet@Ordered+and+Normalized+sets.
909
1149
  #
910
1150
  # Net::IMAP::SequenceSet["2,5:9,6,12:11"].numbers
911
1151
  # #=> [2, 5, 6, 7, 8, 9, 11, 12]
@@ -937,6 +1177,8 @@ module Net
937
1177
  # no sorting, deduplication, or coalescing. When #string is in its
938
1178
  # normalized form, this will yield the same values as #each_element.
939
1179
  #
1180
+ # See SequenceSet@Ordered+and+Normalized+sets.
1181
+ #
940
1182
  # Related: #entries, #each_element
941
1183
  def each_entry(&block) # :yields: integer or range or :*
942
1184
  return to_enum(__method__) unless block_given?
@@ -947,7 +1189,7 @@ module Net
947
1189
  # and returns self. Returns an enumerator when called without a block.
948
1190
  #
949
1191
  # The returned numbers are sorted and de-duplicated, even when the input
950
- # #string is not. See #normalize.
1192
+ # #string is not. See #normalize, SequenceSet@Ordered+and+Normalized+sets.
951
1193
  #
952
1194
  # Related: #elements, #each_entry
953
1195
  def each_element # :yields: integer or range or :*
@@ -1241,20 +1483,76 @@ module Net
1241
1483
  def slice_range(range)
1242
1484
  first = range.begin || 0
1243
1485
  last = range.end || -1
1244
- last -= 1 if range.exclude_end? && range.end && last != STAR_INT
1486
+ if range.exclude_end?
1487
+ return remain_frozen_empty if last.zero?
1488
+ last -= 1 if range.end && last != STAR_INT
1489
+ end
1245
1490
  if (first * last).positive? && last < first
1246
- SequenceSet.empty
1491
+ remain_frozen_empty
1247
1492
  elsif (min = at(first))
1248
1493
  max = at(last)
1494
+ max = :* if max.nil?
1249
1495
  if max == :* then self & (min..)
1250
1496
  elsif min <= max then self & (min..max)
1251
- else SequenceSet.empty
1497
+ else remain_frozen_empty
1252
1498
  end
1253
1499
  end
1254
1500
  end
1255
1501
 
1256
1502
  public
1257
1503
 
1504
+ # Returns a copy of +self+ which only contains the numbers above +num+.
1505
+ #
1506
+ # Net::IMAP::SequenceSet["5,10:22,50"].above(10) # to_s => "11:22,50"
1507
+ # Net::IMAP::SequenceSet["5,10:22,50"].above(20) # to_s => "21:22,50
1508
+ # Net::IMAP::SequenceSet["5,10:22,50"].above(30) # to_s => "50"
1509
+ #
1510
+ # This returns the same result as #intersection with <tt>((num+1)..)</tt>
1511
+ # or #difference with <tt>(..num)</tt>.
1512
+ #
1513
+ # Net::IMAP::SequenceSet["5,10:22,50"] & (11..) # to_s => "11:22,50"
1514
+ # Net::IMAP::SequenceSet["5,10:22,50"] - (..10) # to_s => "11:22,50"
1515
+ # Net::IMAP::SequenceSet["5,10:22,50"] & (21..) # to_s => "21:22,50"
1516
+ # Net::IMAP::SequenceSet["5,10:22,50"] - (..20) # to_s => "21:22,50"
1517
+ #
1518
+ # Related: #above, #-, #&
1519
+ def above(num)
1520
+ NumValidator.valid_nz_number?(num) or
1521
+ raise ArgumentError, "not a valid sequence set number"
1522
+ difference(..num)
1523
+ end
1524
+
1525
+ # Returns a copy of +self+ which only contains numbers below +num+.
1526
+ #
1527
+ # Net::IMAP::SequenceSet["5,10:22,50"].below(10) # to_s => "5"
1528
+ # Net::IMAP::SequenceSet["5,10:22,50"].below(20) # to_s => "5,10:19"
1529
+ # Net::IMAP::SequenceSet["5,10:22,50"].below(30) # to_s => "5,10:22"
1530
+ #
1531
+ # This returns the same result as #intersection with <tt>(..(num-1))</tt>
1532
+ # or #difference with <tt>(num..)</tt>.
1533
+ #
1534
+ # Net::IMAP::SequenceSet["5,10:22,50"] & (..9) # to_s => "5"
1535
+ # Net::IMAP::SequenceSet["5,10:22,50"] - (10..) # to_s => "5"
1536
+ # Net::IMAP::SequenceSet["5,10:22,50"] & (..19) # to_s => "5,10:19"
1537
+ # Net::IMAP::SequenceSet["5,10:22,50"] - (20..) # to_s => "5,10:19"
1538
+ #
1539
+ # When the set does not contain <tt>*</tt>, #below is identical to #limit
1540
+ # with <tt>max: num - 1</tt>. When the set does contain <tt>*</tt>,
1541
+ # #below always drops it from the result. Use #limit when the IMAP
1542
+ # semantics for <tt>*</tt> must be enforced.
1543
+ #
1544
+ # Net::IMAP::SequenceSet["5,10:22,50"].below(30) # to_s => "5,10:22"
1545
+ # Net::IMAP::SequenceSet["5,10:22,50"].limit(max: 29) # to_s => "5,10:22"
1546
+ # Net::IMAP::SequenceSet["5,10:22,*"].below(30) # to_s => "5,10:22"
1547
+ # Net::IMAP::SequenceSet["5,10:22,*"].limit(max: 29) # to_s => "5,10:22,29"
1548
+ #
1549
+ # Related: #above, #-, #&, #limit
1550
+ def below(num)
1551
+ NumValidator.valid_nz_number?(num) or
1552
+ raise ArgumentError, "not a valid sequence set number"
1553
+ difference(num..)
1554
+ end
1555
+
1258
1556
  # Returns a frozen SequenceSet with <tt>*</tt> converted to +max+, numbers
1259
1557
  # and ranges over +max+ removed, and ranges containing +max+ converted to
1260
1558
  # end at +max+.
@@ -1272,6 +1570,7 @@ module Net
1272
1570
  # Net::IMAP::SequenceSet["500:*"].limit(max: 37)
1273
1571
  # #=> Net::IMAP::SequenceSet["37"]
1274
1572
  #
1573
+ # Related: #limit!
1275
1574
  def limit(max:)
1276
1575
  max = to_tuple_int(max)
1277
1576
  if empty? then self.class.empty
@@ -1286,6 +1585,7 @@ module Net
1286
1585
  #
1287
1586
  # Related: #limit
1288
1587
  def limit!(max:)
1588
+ modifying! # short-circuit, and normalize the error message for JRuby
1289
1589
  star = include_star?
1290
1590
  max = to_tuple_int(max)
1291
1591
  tuple_subtract [max + 1, STAR_INT]
@@ -1300,6 +1600,7 @@ module Net
1300
1600
  #
1301
1601
  # Related: #complement
1302
1602
  def complement!
1603
+ modifying! # short-circuit, and normalize the error message for JRuby
1303
1604
  return replace(self.class.full) if empty?
1304
1605
  return clear if full?
1305
1606
  flat = @tuples.flat_map { [_1 - 1, _2 + 1] }
@@ -1313,6 +1614,7 @@ module Net
1313
1614
  #
1314
1615
  # The returned set's #string is sorted and deduplicated. Adjacent or
1315
1616
  # overlapping elements will be merged into a single larger range.
1617
+ # See SequenceSet@Ordered+and+Normalized+sets.
1316
1618
  #
1317
1619
  # Net::IMAP::SequenceSet["1:5,3:7,10:9,10:11"].normalize
1318
1620
  # #=> Net::IMAP::SequenceSet["1:7,9:11"]
@@ -1325,37 +1627,87 @@ module Net
1325
1627
  end
1326
1628
 
1327
1629
  # Resets #string to be sorted, deduplicated, and coalesced. Returns
1328
- # +self+.
1630
+ # +self+. See SequenceSet@Ordered+and+Normalized+sets.
1329
1631
  #
1330
1632
  # Related: #normalize, #normalized_string
1331
1633
  def normalize!
1634
+ modifying! # redundant check, to normalize the error message for JRuby
1332
1635
  @string = nil
1333
1636
  self
1334
1637
  end
1335
1638
 
1336
1639
  # Returns a normalized +sequence-set+ string representation, sorted
1337
1640
  # and deduplicated. Adjacent or overlapping elements will be merged into
1338
- # a single larger range. Returns +nil+ when the set is empty.
1641
+ # a single larger range. See SequenceSet@Ordered+and+Normalized+sets.
1339
1642
  #
1340
1643
  # Net::IMAP::SequenceSet["1:5,3:7,10:9,10:11"].normalized_string
1341
1644
  # #=> "1:7,9:11"
1342
1645
  #
1343
- # Related: #normalize!, #normalize
1646
+ # Returns +nil+ when the set is empty.
1647
+ #
1648
+ # Related: #normalize!, #normalize, #string, #to_s
1344
1649
  def normalized_string
1345
1650
  @tuples.empty? ? nil : -@tuples.map { tuple_to_str _1 }.join(",")
1346
1651
  end
1347
1652
 
1653
+ # Returns an inspection string for the SequenceSet.
1654
+ #
1655
+ # Net::IMAP::SequenceSet.new.inspect
1656
+ # #=> "Net::IMAP::SequenceSet()"
1657
+ #
1658
+ # Net::IMAP::SequenceSet(1..5, 1024, 15, 2000).inspect
1659
+ # #=> 'Net::IMAP::SequenceSet("1:5,15,1024,2000")'
1660
+ #
1661
+ # Frozen sets have slightly different output:
1662
+ #
1663
+ # Net::IMAP::SequenceSet.empty.inspect
1664
+ # #=> "Net::IMAP::SequenceSet.empty"
1665
+ #
1666
+ # Net::IMAP::SequenceSet[1..5, 1024, 15, 2000].inspect
1667
+ # #=> 'Net::IMAP::SequenceSet["1:5,15,1024,2000"]'
1668
+ #
1669
+ # Large sets (by number of #entries) have abridged output, with only the
1670
+ # first and last entries:
1671
+ #
1672
+ # Net::IMAP::SequenceSet(((1..5000) % 2).to_a).inspect
1673
+ # #=> #<Net::IMAP::SequenceSet 2500 entries "1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31,...(2468 entries omitted)...,4969,4971,4973,4975,4977,4979,4981,4983,4985,4987,4989,4991,4993,4995,4997,4999">
1674
+ #
1675
+ # Related: #to_s, #string
1348
1676
  def inspect
1349
- if empty?
1350
- (frozen? ? "%s.empty" : "#<%s empty>") % [self.class]
1351
- elsif frozen?
1352
- "%s[%p]" % [self.class, to_s]
1677
+ case (count = count_entries)
1678
+ when 0
1679
+ (frozen? ? "%s.empty" : "%s()") % [self.class]
1680
+ when ..INSPECT_MAX_LEN
1681
+ (frozen? ? "%s[%p]" : "%s(%p)") % [self.class, to_s]
1353
1682
  else
1354
- "#<%s %p>" % [self.class, to_s]
1683
+ if @string
1684
+ head = @string[INSPECT_ABRIDGED_HEAD_RE]
1685
+ tail = @string[INSPECT_ABRIDGED_TAIL_RE]
1686
+ else
1687
+ head = export_string_entries(@tuples.first(INSPECT_TRUNCATE_LEN)) + ","
1688
+ tail = "," + export_string_entries(@tuples.last(INSPECT_TRUNCATE_LEN))
1689
+ end
1690
+ '#<%s %d entries "%s...(%d entries omitted)...%s"%s>' % [
1691
+ self.class, count,
1692
+ head, count - INSPECT_TRUNCATE_LEN * 2, tail,
1693
+ frozen? ? " (frozen)" : "",
1694
+ ]
1355
1695
  end
1356
1696
  end
1357
1697
 
1358
- # Returns self
1698
+ private def count_entries
1699
+ @string ? @string.count(",") + 1 : @tuples.count
1700
+ end
1701
+
1702
+ ##
1703
+ # :method: to_sequence_set
1704
+ # :call-seq: to_sequence_set -> self
1705
+ #
1706
+ # Returns +self+
1707
+ #
1708
+ # Related: ::try_convert
1709
+
1710
+ # :nodoc: (work around rdoc bug)
1359
1711
  alias to_sequence_set itself
1360
1712
 
1361
1713
  # Unstable API: currently for internal use only (Net::IMAP#validate_data)
@@ -1385,18 +1737,21 @@ module Net
1385
1737
 
1386
1738
  attr_reader :tuples # :nodoc:
1387
1739
 
1740
+ def deep_copy_tuples; @tuples.map { _1.dup } end # :nodoc:
1741
+
1388
1742
  private
1389
1743
 
1390
1744
  def remain_frozen(set) frozen? ? set.freeze : set end
1745
+ def remain_frozen_empty; frozen? ? SequenceSet.empty : SequenceSet.new end
1391
1746
 
1392
1747
  # frozen clones are shallow copied
1393
1748
  def initialize_clone(other)
1394
- other.frozen? ? super : initialize_dup(other)
1749
+ @tuples = other.deep_copy_tuples unless other.frozen?
1750
+ super
1395
1751
  end
1396
1752
 
1397
1753
  def initialize_dup(other)
1398
- @tuples = other.tuples.map(&:dup)
1399
- @string = other.string&.-@
1754
+ @tuples = other.deep_copy_tuples
1400
1755
  super
1401
1756
  end
1402
1757
 
@@ -1421,9 +1776,8 @@ module Net
1421
1776
  when Array then set.flat_map { input_to_tuples _1 }
1422
1777
  when nil then []
1423
1778
  else
1424
- raise DataFormatError,
1425
- "expected nz-number, range, string, or enumerable; " \
1426
- "got %p" % [set]
1779
+ raise DataFormatError, "expected nz-number, range, '*', Set, Array; " \
1780
+ "got %p" % [set]
1427
1781
  end
1428
1782
  end
1429
1783
 
@@ -1449,6 +1803,10 @@ module Net
1449
1803
  def to_tuple_int(obj) STARS.include?(obj) ? STAR_INT : nz_number(obj) end
1450
1804
  def from_tuple_int(num) num == STAR_INT ? :* : num end
1451
1805
 
1806
+ def export_string_entries(entries)
1807
+ -entries.map { tuple_to_str _1 }.join(",")
1808
+ end
1809
+
1452
1810
  def tuple_to_str(tuple) tuple.uniq.map{ from_tuple_int _1 }.join(":") end
1453
1811
  def str_to_tuples(str) str.split(",", -1).map! { str_to_tuple _1 } end
1454
1812
  def str_to_tuple(str)
data/lib/net/imap.rb CHANGED
@@ -788,7 +788,7 @@ module Net
788
788
  # * {IMAP URLAUTH Authorization Mechanism Registry}[https://www.iana.org/assignments/urlauth-authorization-mechanism-registry/urlauth-authorization-mechanism-registry.xhtml]
789
789
  #
790
790
  class IMAP < Protocol
791
- VERSION = "0.5.7"
791
+ VERSION = "0.5.10"
792
792
 
793
793
  # Aliases for supported capabilities, to be used with the #enable command.
794
794
  ENABLE_ALIASES = {
@@ -801,6 +801,7 @@ module Net
801
801
  autoload :ResponseReader, "#{dir}/response_reader"
802
802
  autoload :SASL, "#{dir}/sasl"
803
803
  autoload :SASLAdapter, "#{dir}/sasl_adapter"
804
+ autoload :SequenceSet, "#{dir}/sequence_set"
804
805
  autoload :StringPrep, "#{dir}/stringprep"
805
806
 
806
807
  include MonitorMixin
@@ -809,6 +810,22 @@ module Net
809
810
  include SSL
810
811
  end
811
812
 
813
+ # :call-seq:
814
+ # Net::IMAP::SequenceSet(set = nil) -> SequenceSet
815
+ #
816
+ # Coerces +set+ into a SequenceSet, using either SequenceSet.try_convert or
817
+ # SequenceSet.new.
818
+ #
819
+ # * When +set+ is a SequenceSet, that same set is returned.
820
+ # * When +set+ responds to +to_sequence_set+, +set.to_sequence_set+ is
821
+ # returned.
822
+ # * Otherwise, returns the result from calling SequenceSet.new with +set+.
823
+ #
824
+ # Related: SequenceSet.try_convert, SequenceSet.new, SequenceSet::[]
825
+ def self.SequenceSet(set = nil)
826
+ SequenceSet.try_convert(set) || SequenceSet.new(set)
827
+ end
828
+
812
829
  # Returns the global Config object
813
830
  def self.config; Config.global end
814
831
 
@@ -1114,28 +1131,27 @@ module Net
1114
1131
 
1115
1132
  # Disconnects from the server.
1116
1133
  #
1134
+ # Waits for receiver thread to close before returning. Slow or stuck
1135
+ # response handlers can cause #disconnect to hang until they complete.
1136
+ #
1117
1137
  # Related: #logout, #logout!
1118
1138
  def disconnect
1139
+ in_logout_state = try_state_logout?
1119
1140
  return if disconnected?
1120
- state_logout!
1121
1141
  begin
1122
- begin
1123
- # try to call SSL::SSLSocket#io.
1124
- @sock.io.shutdown
1125
- rescue NoMethodError
1126
- # @sock is not an SSL::SSLSocket.
1127
- @sock.shutdown
1128
- end
1142
+ @sock.to_io.shutdown
1129
1143
  rescue Errno::ENOTCONN
1130
1144
  # ignore `Errno::ENOTCONN: Socket is not connected' on some platforms.
1131
1145
  rescue Exception => e
1132
1146
  @receiver_thread.raise(e)
1133
1147
  end
1148
+ @sock.close
1134
1149
  @receiver_thread.join
1135
- synchronize do
1136
- @sock.close
1137
- end
1138
1150
  raise e if e
1151
+ ensure
1152
+ # Try again after shutting down the receiver thread. With no reciever
1153
+ # left to wait for, any remaining locks should be _very_ brief.
1154
+ state_logout! unless in_logout_state
1139
1155
  end
1140
1156
 
1141
1157
  # Returns true if disconnected from the server.
@@ -3062,8 +3078,8 @@ module Net
3062
3078
  raise @exception || Net::IMAP::Error.new("connection closed")
3063
3079
  end
3064
3080
  ensure
3081
+ remove_response_handler(response_handler)
3065
3082
  unless @receiver_thread_terminating
3066
- remove_response_handler(response_handler)
3067
3083
  put_string("DONE#{CRLF}")
3068
3084
  response = get_tagged_response(tag, "IDLE", idle_response_timeout)
3069
3085
  end
@@ -3346,8 +3362,6 @@ module Net
3346
3362
  rescue Exception => ex
3347
3363
  @receiver_thread_exception = ex
3348
3364
  # don't exit the thread with an exception
3349
- ensure
3350
- state_logout!
3351
3365
  end
3352
3366
  end
3353
3367
 
@@ -3429,6 +3443,8 @@ module Net
3429
3443
  @idle_done_cond.signal
3430
3444
  end
3431
3445
  end
3446
+ ensure
3447
+ state_logout!
3432
3448
  end
3433
3449
 
3434
3450
  def get_tagged_response(tag, cmd, timeout = nil)
@@ -3791,15 +3807,29 @@ module Net
3791
3807
  end
3792
3808
 
3793
3809
  def state_unselected!
3794
- state_authenticated! if connection_state.to_sym == :selected
3810
+ synchronize do
3811
+ state_authenticated! if connection_state.to_sym == :selected
3812
+ end
3795
3813
  end
3796
3814
 
3797
3815
  def state_logout!
3816
+ return true if connection_state in [:logout, *]
3798
3817
  synchronize do
3818
+ return true if connection_state in [:logout, *]
3799
3819
  @connection_state = ConnectionState::Logout.new
3800
3820
  end
3801
3821
  end
3802
3822
 
3823
+ # don't wait to aqcuire the lock
3824
+ def try_state_logout?
3825
+ return true if connection_state in [:logout, *]
3826
+ return false unless acquired_lock = mon_try_enter
3827
+ state_logout!
3828
+ true
3829
+ ensure
3830
+ mon_exit if acquired_lock
3831
+ end
3832
+
3803
3833
  def sasl_adapter
3804
3834
  SASLAdapter.new(self, &method(:send_command_with_continuations))
3805
3835
  end
@@ -388,9 +388,11 @@ class StringPrepTablesGenerator
388
388
  end
389
389
 
390
390
  def asgn_mapping(name, replacement = to_map(tables[name]))
391
+ indent = " " * 2
392
+ replacement = replacement.inspect.gsub(/" => "/, '"=>"')
391
393
  cname = name.tr(?., ?_).upcase
392
- "# Replacements for %s\n%s%s = %p.freeze" % [
393
- "IN_#{name}", " " * 2, "MAP_#{cname}", replacement,
394
+ "# Replacements for %s\n%s%s = %s.freeze" % [
395
+ "IN_#{name}", indent, "MAP_#{cname}", replacement,
394
396
  ]
395
397
  end
396
398
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: net-imap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.7
4
+ version: 0.5.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shugo Maeda
@@ -129,7 +129,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
129
129
  - !ruby/object:Gem::Version
130
130
  version: '0'
131
131
  requirements: []
132
- rubygems_version: 3.6.7
132
+ rubygems_version: 3.6.9
133
133
  specification_version: 4
134
134
  summary: Ruby client api for Internet Message Access Protocol
135
135
  test_files: []