net-imap 0.5.7 → 0.5.9

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: 9a31378c34762136e5fc341801a0b4073157dc6c35df6aae3254d3f7b90a07b7
4
+ data.tar.gz: cbf787e39ecfde5a7af0061baba54eae3d7b12077679854ea62b335fc9b48798
5
5
  SHA512:
6
- metadata.gz: f90a4500f3c218dd3a51cca84a76c620fa8f8488f2ca73486b538a43e67e21e0b897d23d957f490afe1590fd755c692f2e0dede562460ed2cfcd7f4d9bec3262
7
- data.tar.gz: 22abb3cf5699bb715c33c69b596ead46ff400109925f58d633ecdc82797be9b80fbbd040c02aa7cd9286f3aa9c697e9fc426f1115b9019455b2ac4e6667cc236
6
+ metadata.gz: fd0c8a34fa212bb42a55e943bf8184c614256b52da4ac13bdddb48f74c8517f5c7b72addb2153af42d76f2a8bcf42627ceb34e874b7842d8a6dfd542ba577578
7
+ data.tar.gz: 91dd4d1d35582abb9e4fd0df64c63ee5c9320f3404d548bff0a4023e32dc9a6ddd6b90cd1cc221e637a4719f3cc5699d57cc337ea17a6454b3aa7d7be9ffb423
data/Gemfile CHANGED
@@ -14,6 +14,7 @@ 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
18
19
 
19
20
  group :test do
@@ -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
@@ -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"
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,55,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
53
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
110
  # set.valid_string #=> "1:10,55,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
  #
@@ -329,13 +409,12 @@ module Net
329
409
  # An empty SequenceSet is invalid and will raise a DataFormatError.
330
410
  #
331
411
  # Use ::new to create a mutable or empty SequenceSet.
412
+ #
413
+ # Related: ::new, Net::IMAP::SequenceSet(), ::try_convert
332
414
  def [](first, *rest)
333
415
  if rest.empty?
334
- if first.is_a?(SequenceSet) && first.frozen? && first.valid?
335
- first
336
- else
337
- new(first).validate.freeze
338
- end
416
+ set = try_convert(first)&.validate
417
+ set&.frozen? ? set : (set&.dup || new(first).validate).freeze
339
418
  else
340
419
  new(first).merge(*rest).validate.freeze
341
420
  end
@@ -350,6 +429,8 @@ module Net
350
429
  #
351
430
  # If +obj.to_sequence_set+ doesn't return a SequenceSet, an exception is
352
431
  # raised.
432
+ #
433
+ # Related: Net::IMAP::SequenceSet(), ::new, ::[]
353
434
  def try_convert(obj)
354
435
  return obj if obj.is_a?(SequenceSet)
355
436
  return nil unless obj.respond_to?(:to_sequence_set)
@@ -368,20 +449,85 @@ module Net
368
449
  end
369
450
 
370
451
  # 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.
452
+ # SequenceSet, an IMAP formatted +sequence-set+ string, a non-zero 32 bit
453
+ # unsigned integer, a range, <tt>:*</tt>, a Set of numbers or <tt>*</tt>,
454
+ # an object that responds to +to_sequence_set+ (such as SearchResult) or
455
+ # an Array of these (array inputs may be nested).
456
+ #
457
+ # set = Net::IMAP::SequenceSet.new(1)
458
+ # set.valid_string #=> "1"
459
+ # set = Net::IMAP::SequenceSet.new(1..100)
460
+ # set.valid_string #=> "1:100"
461
+ # set = Net::IMAP::SequenceSet.new(1...100)
462
+ # set.valid_string #=> "1:99"
463
+ # set = Net::IMAP::SequenceSet.new([1, 2, 5..])
464
+ # set.valid_string #=> "1:2,5:*"
465
+ # set = Net::IMAP::SequenceSet.new("1,2,3:7,5,6:10,2048,1024")
466
+ # set.valid_string #=> "1,2,3:7,5,6:10,2048,1024"
467
+ # set = Net::IMAP::SequenceSet.new(1, 2, 3..7, 5, 6..10, 2048, 1024)
468
+ # set.valid_string #=> "1:10,55,1024:2048"
469
+ #
470
+ # With no arguments (or +nil+) creates an empty sequence set. Note that
471
+ # an empty sequence set is invalid in the \IMAP grammar.
472
+ #
473
+ # set = Net::IMAP::SequenceSet.new
474
+ # set.empty? #=> true
475
+ # set.valid? #=> false
476
+ # set.valid_string #!> raises DataFormatError
477
+ # set << 1..10
478
+ # set.empty? #=> false
479
+ # set.valid? #=> true
480
+ # set.valid_string #=> "1:10"
481
+ #
482
+ # When +input+ is a SequenceSet, ::new behaves the same as calling #dup on
483
+ # that other set. The input's #string will be preserved.
484
+ #
485
+ # input = Net::IMAP::SequenceSet.new("1,2,3:7,5,6:10,2048,1024")
486
+ # copy = Net::IMAP::SequenceSet.new(input)
487
+ # input.valid_string #=> "1,2,3:7,5,6:10,2048,1024"
488
+ # copy.valid_string #=> "1,2,3:7,5,6:10,2048,1024"
489
+ # copy2 = input.dup # same as calling new with a SequenceSet input
490
+ # copy == input #=> true, same set membership
491
+ # copy.eql? input #=> true, same string value
492
+ # copy.equal? input #=> false, different objects
493
+ #
494
+ # copy.normalize!
495
+ # copy.valid_string #=> "1:10,1024,2048"
496
+ # copy == input #=> true, same set membership
497
+ # copy.eql? input #=> false, different string value
498
+ #
499
+ # copy << 999
500
+ # copy.valid_string #=> "1:10,999,1024,2048"
501
+ # copy == input #=> false, different set membership
502
+ # copy.eql? input #=> false, different string value
503
+ #
504
+ # === Alternative set creation methods
505
+ #
506
+ # * ::[] returns a frozen validated (non-empty) SequenceSet, without
507
+ # allocating a new object when the input is already a valid frozen
508
+ # SequenceSet.
509
+ # * Net::IMAP::SequenceSet() coerces an input to SequenceSet, without
510
+ # allocating a new object when the input is already a SequenceSet.
511
+ # * ::try_convert calls +to_sequence_set+ on inputs that support it and
512
+ # returns +nil+ for inputs that don't.
513
+ # * ::empty and ::full both return frozen singleton sets which can be
514
+ # combined with set operations (#|, #&, #^, #-, etc) to make new sets.
515
+ #
516
+ # See SequenceSet@Creating+sequence+sets.
375
517
  def initialize(input = nil) input ? replace(input) : clear end
376
518
 
377
519
  # Removes all elements and returns self.
378
- def clear; @tuples, @string = [], nil; self end
520
+ def clear
521
+ modifying! # redundant check, to normalize the error message for JRuby
522
+ @tuples, @string = [], nil
523
+ self
524
+ end
379
525
 
380
526
  # Replace the contents of the set with the contents of +other+ and returns
381
527
  # +self+.
382
528
  #
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.
529
+ # +other+ may be another SequenceSet or any other object that would be
530
+ # accepted by ::new.
385
531
  def replace(other)
386
532
  case other
387
533
  when SequenceSet then initialize_dup(other)
@@ -431,11 +577,13 @@ module Net
431
577
  if str.nil?
432
578
  clear
433
579
  else
580
+ modifying! # redundant check, to normalize the error message for JRuby
434
581
  str = String.try_convert(str) or raise ArgumentError, "not a string"
435
582
  tuples = str_to_tuples str
436
583
  @tuples, @string = [], -str
437
584
  tuples_add tuples
438
585
  end
586
+ str
439
587
  end
440
588
 
441
589
  # Returns the \IMAP +sequence-set+ string representation, or an empty
@@ -495,8 +643,9 @@ module Net
495
643
 
496
644
  # :call-seq: self === other -> true | false | nil
497
645
  #
498
- # Returns whether +other+ is contained within the set. Returns +nil+ if a
499
- # StandardError is raised while converting +other+ to a comparable type.
646
+ # Returns whether +other+ is contained within the set. +other+ may be any
647
+ # object that would be accepted by ::new. Returns +nil+ if StandardError
648
+ # is raised while converting +other+ to a comparable type.
500
649
  #
501
650
  # Related: #cover?, #include?, #include_star?
502
651
  def ===(other)
@@ -510,12 +659,12 @@ module Net
510
659
  # Returns whether +other+ is contained within the set. +other+ may be any
511
660
  # object that would be accepted by ::new.
512
661
  #
513
- # Related: #===, #include?, #include_star?
662
+ # Related: #===, #include?, #include_star?, #intersect?
514
663
  def cover?(other) input_to_tuples(other).none? { !include_tuple?(_1) } end
515
664
 
516
665
  # 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>.
666
+ # otherwise. Returns +nil+ when +number+ isn't a valid SequenceSet
667
+ # element (Integer, Range, <tt>*</tt>, +sequence-set+ string).
519
668
  #
520
669
  # set = Net::IMAP::SequenceSet["5:10,100,111:115"]
521
670
  # set.include? 1 #=> false
@@ -523,8 +672,8 @@ module Net
523
672
  # set.include? 11..20 #=> false
524
673
  # set.include? 100 #=> true
525
674
  # 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
675
+ # set.include? 6..9 #=> true, covered by "5:10"
676
+ # set.include? "6:9" #=> true, strings are parsed
528
677
  # set.include? 4..9 #=> false, intersection is not sufficient
529
678
  # set.include? "*" #=> false, use #limit to re-interpret "*"
530
679
  # set.include? -1 #=> false, -1 is interpreted as "*"
@@ -533,11 +682,14 @@ module Net
533
682
  # set.include? :* #=> true
534
683
  # set.include? "*" #=> true
535
684
  # set.include? -1 #=> true
536
- # set.include? 200.. #=> true
537
- # set.include? 100.. #=> false
685
+ # set.include?(200..) #=> true
686
+ # set.include?(100..) #=> false
538
687
  #
539
- # Related: #include_star?, #cover?, #===
540
- def include?(element) include_tuple? input_to_tuple element end
688
+ # Related: #include_star?, #cover?, #===, #intersect?
689
+ def include?(element)
690
+ tuple = input_to_tuple element rescue nil
691
+ !!include_tuple?(tuple) if tuple
692
+ end
541
693
 
542
694
  alias member? include?
543
695
 
@@ -550,7 +702,7 @@ module Net
550
702
  # Net::IMAP::SequenceSet["5:10"].intersect? "7,9,11" #=> true
551
703
  # Net::IMAP::SequenceSet["5:10"].intersect? "11:33" #=> false
552
704
  #
553
- # Related: #intersection, #disjoint?
705
+ # Related: #intersection, #disjoint?, #cover?, #include?
554
706
  def intersect?(other)
555
707
  valid? && input_to_tuples(other).any? { intersect_tuple? _1 }
556
708
  end
@@ -567,26 +719,53 @@ module Net
567
719
  empty? || input_to_tuples(other).none? { intersect_tuple? _1 }
568
720
  end
569
721
 
570
- # :call-seq: max(star: :*) => integer or star or nil
722
+ # :call-seq:
723
+ # max(star: :*) => integer or star or nil
724
+ # max(count) => SequenceSet
571
725
  #
572
726
  # Returns the maximum value in +self+, +star+ when the set includes
573
727
  # <tt>*</tt>, or +nil+ when the set is empty.
574
- def max(star: :*)
575
- (val = @tuples.last&.last) && val == STAR_INT ? star : val
728
+ #
729
+ # When +count+ is given, a new SequenceSet is returned, containing only
730
+ # the last +count+ numbers. An empty SequenceSet is returned when +self+
731
+ # is empty. (+star+ is ignored when +count+ is given.)
732
+ #
733
+ # Related: #min, #minmax, #slice
734
+ def max(count = nil, star: :*)
735
+ if count
736
+ slice(-[count, size].min..) || remain_frozen_empty
737
+ elsif (val = @tuples.last&.last)
738
+ val == STAR_INT ? star : val
739
+ end
576
740
  end
577
741
 
578
- # :call-seq: min(star: :*) => integer or star or nil
742
+ # :call-seq:
743
+ # min(star: :*) => integer or star or nil
744
+ # min(count) => SequenceSet
579
745
  #
580
746
  # Returns the minimum value in +self+, +star+ when the only value in the
581
747
  # 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
748
+ #
749
+ # When +count+ is given, a new SequenceSet is returned, containing only
750
+ # the first +count+ numbers. An empty SequenceSet is returned when +self+
751
+ # is empty. (+star+ is ignored when +count+ is given.)
752
+ #
753
+ # Related: #max, #minmax, #slice
754
+ def min(count = nil, star: :*)
755
+ if count
756
+ slice(0...count) || remain_frozen_empty
757
+ elsif (val = @tuples.first&.first)
758
+ val != STAR_INT ? val : star
759
+ end
584
760
  end
585
761
 
586
- # :call-seq: minmax(star: :*) => nil or [integer, integer or star]
762
+ # :call-seq: minmax(star: :*) => [min, max] or nil
587
763
  #
588
764
  # Returns a 2-element array containing the minimum and maximum numbers in
589
- # +self+, or +nil+ when the set is empty.
765
+ # +self+, or +nil+ when the set is empty. +star+ is handled the same way
766
+ # as by #min and #max.
767
+ #
768
+ # Related: #min, #max
590
769
  def minmax(star: :*); [min(star: star), max(star: star)] unless empty? end
591
770
 
592
771
  # Returns false when the set is empty.
@@ -606,14 +785,19 @@ module Net
606
785
  # Returns a new sequence set that has every number in the +other+ object
607
786
  # added.
608
787
  #
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.
788
+ # +other+ may be any object that would be accepted by ::new.
612
789
  #
613
790
  # Net::IMAP::SequenceSet["1:5"] | 2 | [4..6, 99]
614
791
  # #=> Net::IMAP::SequenceSet["1:6,99"]
615
792
  #
616
- # Related: #add, #merge
793
+ # Related: #add, #merge, #&, #-, #^, #~
794
+ #
795
+ # ==== Set identities
796
+ #
797
+ # <tt>lhs | rhs</tt> is equivalent to:
798
+ # * <tt>rhs | lhs</tt> (commutative)
799
+ # * <tt>~(~lhs & ~rhs)</tt> (De Morgan's Law)
800
+ # * <tt>(lhs & rhs) ^ (lhs ^ rhs)</tt>
617
801
  def |(other) remain_frozen dup.merge other end
618
802
  alias :+ :|
619
803
  alias union :|
@@ -625,14 +809,22 @@ module Net
625
809
  # Returns a new sequence set built by duplicating this set and removing
626
810
  # every number that appears in +other+.
627
811
  #
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.
812
+ # +other+ may be any object that would be accepted by ::new.
631
813
  #
632
814
  # Net::IMAP::SequenceSet[1..5] - 2 - 4 - 6
633
815
  # #=> Net::IMAP::SequenceSet["1,3,5"]
634
816
  #
635
- # Related: #subtract
817
+ # Related: #subtract, #|, #&, #^, #~
818
+ #
819
+ # ==== Set identities
820
+ #
821
+ # <tt>lhs - rhs</tt> is equivalent to:
822
+ # * <tt>~rhs - ~lhs</tt>
823
+ # * <tt>lhs & ~rhs</tt>
824
+ # * <tt>~(~lhs | rhs)</tt>
825
+ # * <tt>lhs & (lhs ^ rhs)</tt>
826
+ # * <tt>lhs ^ (lhs & rhs)</tt>
827
+ # * <tt>rhs ^ (lhs | rhs)</tt>
636
828
  def -(other) remain_frozen dup.subtract other end
637
829
  alias difference :-
638
830
 
@@ -643,14 +835,22 @@ module Net
643
835
  # Returns a new sequence set containing only the numbers common to this
644
836
  # set and +other+.
645
837
  #
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.
838
+ # +other+ may be any object that would be accepted by ::new.
649
839
  #
650
840
  # Net::IMAP::SequenceSet[1..5] & [2, 4, 6]
651
841
  # #=> Net::IMAP::SequenceSet["2,4"]
652
842
  #
653
- # <tt>(seqset & other)</tt> is equivalent to <tt>(seqset - ~other)</tt>.
843
+ # Related: #intersect?, #|, #-, #^, #~
844
+ #
845
+ # ==== Set identities
846
+ #
847
+ # <tt>lhs & rhs</tt> is equivalent to:
848
+ # * <tt>rhs & lhs</tt> (commutative)
849
+ # * <tt>~(~lhs | ~rhs)</tt> (De Morgan's Law)
850
+ # * <tt>lhs - ~rhs</tt>
851
+ # * <tt>lhs - (lhs - rhs)</tt>
852
+ # * <tt>lhs - (lhs ^ rhs)</tt>
853
+ # * <tt>lhs ^ (lhs - rhs)</tt>
654
854
  def &(other)
655
855
  remain_frozen dup.subtract SequenceSet.new(other).complement!
656
856
  end
@@ -663,16 +863,22 @@ module Net
663
863
  # Returns a new sequence set containing numbers that are exclusive between
664
864
  # this set and +other+.
665
865
  #
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.
866
+ # +other+ may be any object that would be accepted by ::new.
669
867
  #
670
868
  # Net::IMAP::SequenceSet[1..5] ^ [2, 4, 6]
671
869
  # #=> Net::IMAP::SequenceSet["1,3,5:6"]
672
870
  #
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
871
+ # Related: #|, #&, #-, #~
872
+ #
873
+ # ==== Set identities
874
+ #
875
+ # <tt>lhs ^ rhs</tt> is equivalent to:
876
+ # * <tt>rhs ^ lhs</tt> (commutative)
877
+ # * <tt>~lhs ^ ~rhs</tt>
878
+ # * <tt>(lhs | rhs) - (lhs & rhs)</tt>
879
+ # * <tt>(lhs - rhs) | (rhs - lhs)</tt>
880
+ # * <tt>(lhs ^ other) ^ (other ^ rhs)</tt>
881
+ def ^(other) remain_frozen (dup | other).subtract(self & other) end
676
882
  alias xor :^
677
883
 
678
884
  # :call-seq:
@@ -689,7 +895,12 @@ module Net
689
895
  # ~Net::IMAP::SequenceSet["6:99,223:*"]
690
896
  # #=> Net::IMAP::SequenceSet["1:5,100:222"]
691
897
  #
692
- # Related: #complement!
898
+ # Related: #complement!, #|, #&, #-, #^
899
+ #
900
+ # ==== Set identities
901
+ #
902
+ # <tt>~set</tt> is equivalent to:
903
+ # * <tt>full - set</tt>, where "full" is Net::IMAP::SequenceSet.full
693
904
  def ~; remain_frozen dup.complement! end
694
905
  alias complement :~
695
906
 
@@ -701,8 +912,12 @@ module Net
701
912
  #
702
913
  # #string will be regenerated. Use #merge to add many elements at once.
703
914
  #
704
- # Related: #add?, #merge, #union
915
+ # Use #append to append new elements to #string. See
916
+ # SequenceSet@Ordered+and+Normalized+sets.
917
+ #
918
+ # Related: #add?, #merge, #union, #append
705
919
  def add(element)
920
+ modifying! # short-circuit before input_to_tuple
706
921
  tuple_add input_to_tuple element
707
922
  normalize!
708
923
  end
@@ -712,8 +927,12 @@ module Net
712
927
  #
713
928
  # Unlike #add, #merge, or #union, the new value is appended to #string.
714
929
  # This may result in a #string which has duplicates or is out-of-order.
930
+ #
931
+ # See SequenceSet@Ordered+and+Normalized+sets.
932
+ #
933
+ # Related: #add, #merge, #union
715
934
  def append(entry)
716
- modifying!
935
+ modifying! # short-circuit before input_to_tuple
717
936
  tuple = input_to_tuple entry
718
937
  entry = tuple_to_str tuple
719
938
  string unless empty? # write @string before tuple_add
@@ -731,6 +950,7 @@ module Net
731
950
  #
732
951
  # Related: #add, #merge, #union, #include?
733
952
  def add?(element)
953
+ modifying! # short-circuit before include?
734
954
  add element unless include? element
735
955
  end
736
956
 
@@ -743,6 +963,7 @@ module Net
743
963
  #
744
964
  # Related: #delete?, #delete_at, #subtract, #difference
745
965
  def delete(element)
966
+ modifying! # short-circuit before input_to_tuple
746
967
  tuple_subtract input_to_tuple element
747
968
  normalize!
748
969
  end
@@ -780,6 +1001,7 @@ module Net
780
1001
  #
781
1002
  # Related: #delete, #delete_at, #subtract, #difference, #disjoint?
782
1003
  def delete?(element)
1004
+ modifying! # short-circuit before input_to_tuple
783
1005
  tuple = input_to_tuple element
784
1006
  if tuple.first == tuple.last
785
1007
  return unless include_tuple? tuple
@@ -820,6 +1042,7 @@ module Net
820
1042
  #
821
1043
  # Related: #slice, #delete_at, #delete, #delete?, #subtract, #difference
822
1044
  def slice!(index, length = nil)
1045
+ modifying! # short-circuit before slice
823
1046
  deleted = slice(index, length) and subtract deleted
824
1047
  deleted
825
1048
  end
@@ -827,14 +1050,13 @@ module Net
827
1050
  # Merges all of the elements that appear in any of the +sets+ into the
828
1051
  # set, and returns +self+.
829
1052
  #
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.
1053
+ # The +sets+ may be any objects that would be accepted by ::new.
833
1054
  #
834
1055
  # #string will be regenerated after all sets have been merged.
835
1056
  #
836
1057
  # Related: #add, #add?, #union
837
1058
  def merge(*sets)
1059
+ modifying! # short-circuit before input_to_tuples
838
1060
  tuples_add input_to_tuples sets
839
1061
  normalize!
840
1062
  end
@@ -842,9 +1064,7 @@ module Net
842
1064
  # Removes all of the elements that appear in any of the given +sets+ from
843
1065
  # the set, and returns +self+.
844
1066
  #
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.
1067
+ # The +sets+ may be any objects that would be accepted by ::new.
848
1068
  #
849
1069
  # Related: #difference
850
1070
  def subtract(*sets)
@@ -860,21 +1080,21 @@ module Net
860
1080
  # This is useful when the given order is significant, for example in a
861
1081
  # ESEARCH response to IMAP#sort.
862
1082
  #
1083
+ # See SequenceSet@Ordered+and+Normalized+sets.
1084
+ #
863
1085
  # Related: #each_entry, #elements
864
1086
  def entries; each_entry.to_a end
865
1087
 
866
1088
  # Returns an array of ranges and integers and <tt>:*</tt>.
867
1089
  #
868
1090
  # The returned elements are sorted and coalesced, even when the input
869
- # #string is not. <tt>*</tt> will sort last. See #normalize.
1091
+ # #string is not. <tt>*</tt> will sort last. See #normalize,
1092
+ # SequenceSet@Ordered+and+Normalized+sets.
870
1093
  #
871
1094
  # By itself, <tt>*</tt> translates to <tt>:*</tt>. A range containing
872
1095
  # <tt>*</tt> translates to an endless range. Use #limit to translate both
873
1096
  # cases to a maximum value.
874
1097
  #
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
1098
  # Net::IMAP::SequenceSet["2,5:9,6,*,12:11"].elements
879
1099
  # #=> [2, 5..9, 11..12, :*]
880
1100
  #
@@ -885,15 +1105,13 @@ module Net
885
1105
  # Returns an array of ranges
886
1106
  #
887
1107
  # The returned elements are sorted and coalesced, even when the input
888
- # #string is not. <tt>*</tt> will sort last. See #normalize.
1108
+ # #string is not. <tt>*</tt> will sort last. See #normalize,
1109
+ # SequenceSet@Ordered+and+Normalized+sets.
889
1110
  #
890
1111
  # <tt>*</tt> translates to an endless range. By itself, <tt>*</tt>
891
1112
  # translates to <tt>:*..</tt>. Use #limit to set <tt>*</tt> to a maximum
892
1113
  # value.
893
1114
  #
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
1115
  # Net::IMAP::SequenceSet["2,5:9,6,*,12:11"].ranges
898
1116
  # #=> [2..2, 5..9, 11..12, :*..]
899
1117
  # Net::IMAP::SequenceSet["123,999:*,456:789"].ranges
@@ -905,7 +1123,7 @@ module Net
905
1123
  # Returns a sorted array of all of the number values in the sequence set.
906
1124
  #
907
1125
  # The returned numbers are sorted and de-duplicated, even when the input
908
- # #string is not. See #normalize.
1126
+ # #string is not. See #normalize, SequenceSet@Ordered+and+Normalized+sets.
909
1127
  #
910
1128
  # Net::IMAP::SequenceSet["2,5:9,6,12:11"].numbers
911
1129
  # #=> [2, 5, 6, 7, 8, 9, 11, 12]
@@ -937,6 +1155,8 @@ module Net
937
1155
  # no sorting, deduplication, or coalescing. When #string is in its
938
1156
  # normalized form, this will yield the same values as #each_element.
939
1157
  #
1158
+ # See SequenceSet@Ordered+and+Normalized+sets.
1159
+ #
940
1160
  # Related: #entries, #each_element
941
1161
  def each_entry(&block) # :yields: integer or range or :*
942
1162
  return to_enum(__method__) unless block_given?
@@ -947,7 +1167,7 @@ module Net
947
1167
  # and returns self. Returns an enumerator when called without a block.
948
1168
  #
949
1169
  # The returned numbers are sorted and de-duplicated, even when the input
950
- # #string is not. See #normalize.
1170
+ # #string is not. See #normalize, SequenceSet@Ordered+and+Normalized+sets.
951
1171
  #
952
1172
  # Related: #elements, #each_entry
953
1173
  def each_element # :yields: integer or range or :*
@@ -1241,20 +1461,76 @@ module Net
1241
1461
  def slice_range(range)
1242
1462
  first = range.begin || 0
1243
1463
  last = range.end || -1
1244
- last -= 1 if range.exclude_end? && range.end && last != STAR_INT
1464
+ if range.exclude_end?
1465
+ return remain_frozen_empty if last.zero?
1466
+ last -= 1 if range.end && last != STAR_INT
1467
+ end
1245
1468
  if (first * last).positive? && last < first
1246
- SequenceSet.empty
1469
+ remain_frozen_empty
1247
1470
  elsif (min = at(first))
1248
1471
  max = at(last)
1472
+ max = :* if max.nil?
1249
1473
  if max == :* then self & (min..)
1250
1474
  elsif min <= max then self & (min..max)
1251
- else SequenceSet.empty
1475
+ else remain_frozen_empty
1252
1476
  end
1253
1477
  end
1254
1478
  end
1255
1479
 
1256
1480
  public
1257
1481
 
1482
+ # Returns a copy of +self+ which only contains the numbers above +num+.
1483
+ #
1484
+ # Net::IMAP::SequenceSet["5,10:22,50"].above(10) # to_s => "11:22,50"
1485
+ # Net::IMAP::SequenceSet["5,10:22,50"].above(20) # to_s => "21:22,50
1486
+ # Net::IMAP::SequenceSet["5,10:22,50"].above(30) # to_s => "50"
1487
+ #
1488
+ # This returns the same result as #intersection with <tt>((num+1)..)</tt>
1489
+ # or #difference with <tt>(..num)</tt>.
1490
+ #
1491
+ # Net::IMAP::SequenceSet["5,10:22,50"] & (11..) # to_s => "11:22,50"
1492
+ # Net::IMAP::SequenceSet["5,10:22,50"] - (..10) # to_s => "11:22,50"
1493
+ # Net::IMAP::SequenceSet["5,10:22,50"] & (21..) # to_s => "21:22,50"
1494
+ # Net::IMAP::SequenceSet["5,10:22,50"] - (..20) # to_s => "21:22,50"
1495
+ #
1496
+ # Related: #above, #-, #&
1497
+ def above(num)
1498
+ NumValidator.valid_nz_number?(num) or
1499
+ raise ArgumentError, "not a valid sequence set number"
1500
+ difference(..num)
1501
+ end
1502
+
1503
+ # Returns a copy of +self+ which only contains numbers below +num+.
1504
+ #
1505
+ # Net::IMAP::SequenceSet["5,10:22,50"].below(10) # to_s => "5"
1506
+ # Net::IMAP::SequenceSet["5,10:22,50"].below(20) # to_s => "5,10:19"
1507
+ # Net::IMAP::SequenceSet["5,10:22,50"].below(30) # to_s => "5,10:22"
1508
+ #
1509
+ # This returns the same result as #intersection with <tt>(..(num-1))</tt>
1510
+ # or #difference with <tt>(num..)</tt>.
1511
+ #
1512
+ # Net::IMAP::SequenceSet["5,10:22,50"] & (..9) # to_s => "5"
1513
+ # Net::IMAP::SequenceSet["5,10:22,50"] - (10..) # to_s => "5"
1514
+ # Net::IMAP::SequenceSet["5,10:22,50"] & (..19) # to_s => "5,10:19"
1515
+ # Net::IMAP::SequenceSet["5,10:22,50"] - (20..) # to_s => "5,10:19"
1516
+ #
1517
+ # When the set does not contain <tt>*</tt>, #below is identical to #limit
1518
+ # with <tt>max: num - 1</tt>. When the set does contain <tt>*</tt>,
1519
+ # #below always drops it from the result. Use #limit when the IMAP
1520
+ # semantics for <tt>*</tt> must be enforced.
1521
+ #
1522
+ # Net::IMAP::SequenceSet["5,10:22,50"].below(30) # to_s => "5,10:22"
1523
+ # Net::IMAP::SequenceSet["5,10:22,50"].limit(max: 29) # to_s => "5,10:22"
1524
+ # Net::IMAP::SequenceSet["5,10:22,*"].below(30) # to_s => "5,10:22"
1525
+ # Net::IMAP::SequenceSet["5,10:22,*"].limit(max: 29) # to_s => "5,10:22,29"
1526
+ #
1527
+ # Related: #above, #-, #&, #limit
1528
+ def below(num)
1529
+ NumValidator.valid_nz_number?(num) or
1530
+ raise ArgumentError, "not a valid sequence set number"
1531
+ difference(num..)
1532
+ end
1533
+
1258
1534
  # Returns a frozen SequenceSet with <tt>*</tt> converted to +max+, numbers
1259
1535
  # and ranges over +max+ removed, and ranges containing +max+ converted to
1260
1536
  # end at +max+.
@@ -1272,6 +1548,7 @@ module Net
1272
1548
  # Net::IMAP::SequenceSet["500:*"].limit(max: 37)
1273
1549
  # #=> Net::IMAP::SequenceSet["37"]
1274
1550
  #
1551
+ # Related: #limit!
1275
1552
  def limit(max:)
1276
1553
  max = to_tuple_int(max)
1277
1554
  if empty? then self.class.empty
@@ -1286,6 +1563,7 @@ module Net
1286
1563
  #
1287
1564
  # Related: #limit
1288
1565
  def limit!(max:)
1566
+ modifying! # short-circuit, and normalize the error message for JRuby
1289
1567
  star = include_star?
1290
1568
  max = to_tuple_int(max)
1291
1569
  tuple_subtract [max + 1, STAR_INT]
@@ -1300,6 +1578,7 @@ module Net
1300
1578
  #
1301
1579
  # Related: #complement
1302
1580
  def complement!
1581
+ modifying! # short-circuit, and normalize the error message for JRuby
1303
1582
  return replace(self.class.full) if empty?
1304
1583
  return clear if full?
1305
1584
  flat = @tuples.flat_map { [_1 - 1, _2 + 1] }
@@ -1313,6 +1592,7 @@ module Net
1313
1592
  #
1314
1593
  # The returned set's #string is sorted and deduplicated. Adjacent or
1315
1594
  # overlapping elements will be merged into a single larger range.
1595
+ # See SequenceSet@Ordered+and+Normalized+sets.
1316
1596
  #
1317
1597
  # Net::IMAP::SequenceSet["1:5,3:7,10:9,10:11"].normalize
1318
1598
  # #=> Net::IMAP::SequenceSet["1:7,9:11"]
@@ -1325,21 +1605,24 @@ module Net
1325
1605
  end
1326
1606
 
1327
1607
  # Resets #string to be sorted, deduplicated, and coalesced. Returns
1328
- # +self+.
1608
+ # +self+. See SequenceSet@Ordered+and+Normalized+sets.
1329
1609
  #
1330
1610
  # Related: #normalize, #normalized_string
1331
1611
  def normalize!
1612
+ modifying! # redundant check, to normalize the error message for JRuby
1332
1613
  @string = nil
1333
1614
  self
1334
1615
  end
1335
1616
 
1336
1617
  # Returns a normalized +sequence-set+ string representation, sorted
1337
1618
  # and deduplicated. Adjacent or overlapping elements will be merged into
1338
- # a single larger range. Returns +nil+ when the set is empty.
1619
+ # a single larger range. See SequenceSet@Ordered+and+Normalized+sets.
1339
1620
  #
1340
1621
  # Net::IMAP::SequenceSet["1:5,3:7,10:9,10:11"].normalized_string
1341
1622
  # #=> "1:7,9:11"
1342
1623
  #
1624
+ # Returns +nil+ when the set is empty.
1625
+ #
1343
1626
  # Related: #normalize!, #normalize
1344
1627
  def normalized_string
1345
1628
  @tuples.empty? ? nil : -@tuples.map { tuple_to_str _1 }.join(",")
@@ -1355,7 +1638,15 @@ module Net
1355
1638
  end
1356
1639
  end
1357
1640
 
1358
- # Returns self
1641
+ ##
1642
+ # :method: to_sequence_set
1643
+ # :call-seq: to_sequence_set -> self
1644
+ #
1645
+ # Returns +self+
1646
+ #
1647
+ # Related: ::try_convert
1648
+
1649
+ # :nodoc: (work around rdoc bug)
1359
1650
  alias to_sequence_set itself
1360
1651
 
1361
1652
  # Unstable API: currently for internal use only (Net::IMAP#validate_data)
@@ -1388,6 +1679,7 @@ module Net
1388
1679
  private
1389
1680
 
1390
1681
  def remain_frozen(set) frozen? ? set.freeze : set end
1682
+ def remain_frozen_empty; frozen? ? SequenceSet.empty : SequenceSet.new end
1391
1683
 
1392
1684
  # frozen clones are shallow copied
1393
1685
  def initialize_clone(other)
@@ -1395,6 +1687,7 @@ module Net
1395
1687
  end
1396
1688
 
1397
1689
  def initialize_dup(other)
1690
+ modifying! # redundant check, to normalize the error message for JRuby
1398
1691
  @tuples = other.tuples.map(&:dup)
1399
1692
  @string = other.string&.-@
1400
1693
  super
@@ -1421,9 +1714,8 @@ module Net
1421
1714
  when Array then set.flat_map { input_to_tuples _1 }
1422
1715
  when nil then []
1423
1716
  else
1424
- raise DataFormatError,
1425
- "expected nz-number, range, string, or enumerable; " \
1426
- "got %p" % [set]
1717
+ raise DataFormatError, "expected nz-number, range, '*', Set, Array; " \
1718
+ "got %p" % [set]
1427
1719
  end
1428
1720
  end
1429
1721
 
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.9"
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.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shugo Maeda