Skip to content

Commit 2df9106

Browse files
committed
✨ Add SequenceSet#entries for unsorted iteration
Unsorted access is especially important for using with the ESORT or CONTEXT=SORT extensions.
1 parent 86c0ddb commit 2df9106

File tree

2 files changed

+83
-14
lines changed

2 files changed

+83
-14
lines changed

lib/net/imap/sequence_set.rb

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -186,10 +186,14 @@ class IMAP
186186
#
187187
# === Methods for Iterating
188188
#
189-
# - #each_element: Yields each number and range in the set and returns
190-
# +self+.
191-
# - #elements (aliased as #to_a):
192-
# Returns an Array of every number and range in the set.
189+
# - #each_element: Yields each number and range in the set, sorted and
190+
# coalesced, and returns +self+.
191+
# - #elements (aliased as #to_a): Returns an Array of every number and range
192+
# in the set, sorted and coalesced.
193+
# - #each_entry: Yields each number and range in the set, unsorted and
194+
# without deduplicating numbers or coalescing ranges, and returns +self+.
195+
# - #entries: Returns an Array of every number and range in the set,
196+
# unsorted and without deduplicating numbers or coalescing ranges.
193197
# - #each_range:
194198
# Yields each element in the set as a Range and returns +self+.
195199
# - #ranges: Returns an Array of every element in the set, converting
@@ -788,7 +792,18 @@ def subtract(*objects)
788792
normalize!
789793
end
790794

791-
# Returns an array of ranges and integers.
795+
# Returns an array of ranges and integers and <tt>:*</tt>.
796+
#
797+
# The entries are in the same order they appear in #string, with no
798+
# sorting, deduplication, or coalescing. When #string is in its
799+
# normalized form, this will return the same result as #elements.
800+
# This is useful when the given order is significant, for example in a
801+
# ESEARCH response to IMAP#sort.
802+
#
803+
# Related: #each_entry, #elements
804+
def entries; each_entry.to_a end
805+
806+
# Returns an array of ranges and integers and <tt>:*</tt>.
792807
#
793808
# The returned elements are sorted and coalesced, even when the input
794809
# #string is not. <tt>*</tt> will sort last. See #normalize.
@@ -855,22 +870,42 @@ def ranges; each_range.to_a end
855870
# Related: #elements, #ranges, #to_set
856871
def numbers; each_number.to_a end
857872

858-
# Yields each number or range in #elements to the block and returns self.
873+
# Yields each number or range in #string to the block and returns +self+.
859874
# Returns an enumerator when called without a block.
860875
#
861-
# Related: #elements
876+
# The entries are yielded in the same order they appear in #tring, with no
877+
# sorting, deduplication, or coalescing. When #string is in its
878+
# normalized form, this will yield the same values as #each_element.
879+
#
880+
# Related: #entries, #each_element
881+
def each_entry(&block)
882+
return to_enum(__method__) unless block_given?
883+
return each_element(&block) unless @string
884+
@string.split(",").each do yield tuple_to_entry str_to_tuple _1 end
885+
self
886+
end
887+
888+
# Yields each number or range (or <tt>:*</tt>) in #elements to the block
889+
# and returns self. Returns an enumerator when called without a block.
890+
#
891+
# The returned numbers are sorted and de-duplicated, even when the input
892+
# #string is not. See #normalize.
893+
#
894+
# Related: #elements, #each_entry
862895
def each_element # :yields: integer or range or :*
863896
return to_enum(__method__) unless block_given?
864-
@tuples.each do |min, max|
865-
if min == STAR_INT then yield :*
866-
elsif max == STAR_INT then yield min..
867-
elsif min == max then yield min
868-
else yield min..max
869-
end
870-
end
897+
@tuples.each do yield tuple_to_entry _1 end
871898
self
872899
end
873900

901+
private def tuple_to_entry((min, max))
902+
if min == STAR_INT then :*
903+
elsif max == STAR_INT then min..
904+
elsif min == max then min
905+
else min..max
906+
end
907+
end
908+
874909
# Yields each range in #ranges to the block and returns self.
875910
# Returns an enumerator when called without a block.
876911
#

test/net/imap/test_sequence_set.rb

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,7 @@ def test_inspect((expected, input, freeze))
525525
data "single number", {
526526
input: "123456",
527527
elements: [123_456],
528+
entries: [123_456],
528529
ranges: [123_456..123_456],
529530
numbers: [123_456],
530531
to_s: "123456",
@@ -536,6 +537,7 @@ def test_inspect((expected, input, freeze))
536537
data "single range", {
537538
input: "1:3",
538539
elements: [1..3],
540+
entries: [1..3],
539541
ranges: [1..3],
540542
numbers: [1, 2, 3],
541543
to_s: "1:3",
@@ -547,6 +549,7 @@ def test_inspect((expected, input, freeze))
547549
data "simple numbers list", {
548550
input: "1,3,5",
549551
elements: [ 1, 3, 5],
552+
entries: [ 1, 3, 5],
550553
ranges: [1..1, 3..3, 5..5],
551554
numbers: [ 1, 3, 5],
552555
to_s: "1,3,5",
@@ -558,6 +561,7 @@ def test_inspect((expected, input, freeze))
558561
data "numbers and ranges list", {
559562
input: "1:3,5,7:9,46",
560563
elements: [1..3, 5, 7..9, 46],
564+
entries: [1..3, 5, 7..9, 46],
561565
ranges: [1..3, 5..5, 7..9, 46..46],
562566
numbers: [1, 2, 3, 5, 7, 8, 9, 46],
563567
to_s: "1:3,5,7:9,46",
@@ -569,6 +573,7 @@ def test_inspect((expected, input, freeze))
569573
data "just *", {
570574
input: "*",
571575
elements: [:*],
576+
entries: [:*],
572577
ranges: [:*..],
573578
numbers: RangeError,
574579
to_s: "*",
@@ -580,6 +585,7 @@ def test_inspect((expected, input, freeze))
580585
data "range with *", {
581586
input: "4294967000:*",
582587
elements: [4_294_967_000..],
588+
entries: [4_294_967_000..],
583589
ranges: [4_294_967_000..],
584590
numbers: RangeError,
585591
to_s: "4294967000:*",
@@ -591,6 +597,7 @@ def test_inspect((expected, input, freeze))
591597
data "* sorts last", {
592598
input: "5,*,7",
593599
elements: [5, 7, :*],
600+
entries: [5, :*, 7],
594601
ranges: [5..5, 7..7, :*..],
595602
numbers: RangeError,
596603
to_s: "5,*,7",
@@ -602,6 +609,7 @@ def test_inspect((expected, input, freeze))
602609
data "out of order", {
603610
input: "46,7:6,15,3:1",
604611
elements: [1..3, 6..7, 15, 46],
612+
entries: [46, 6..7, 15, 1..3],
605613
ranges: [1..3, 6..7, 15..15, 46..46],
606614
numbers: [1, 2, 3, 6, 7, 15, 46],
607615
to_s: "46,7:6,15,3:1",
@@ -613,6 +621,7 @@ def test_inspect((expected, input, freeze))
613621
data "adjacent", {
614622
input: "1,2,3,5,7:9,10:11",
615623
elements: [1..3, 5, 7..11],
624+
entries: [1, 2, 3, 5, 7..9, 10..11],
616625
ranges: [1..3, 5..5, 7..11],
617626
numbers: [1, 2, 3, 5, 7, 8, 9, 10, 11],
618627
to_s: "1,2,3,5,7:9,10:11",
@@ -624,6 +633,7 @@ def test_inspect((expected, input, freeze))
624633
data "overlapping", {
625634
input: "1:5,3:7,10:9,10:11",
626635
elements: [1..7, 9..11],
636+
entries: [1..5, 3..7, 9..10, 10..11],
627637
ranges: [1..7, 9..11],
628638
numbers: [1, 2, 3, 4, 5, 6, 7, 9, 10, 11],
629639
to_s: "1:5,3:7,10:9,10:11",
@@ -635,6 +645,7 @@ def test_inspect((expected, input, freeze))
635645
data "contained", {
636646
input: "1:5,3:4,9:11,10",
637647
elements: [1..5, 9..11],
648+
entries: [1..5, 3..4, 9..11, 10],
638649
ranges: [1..5, 9..11],
639650
numbers: [1, 2, 3, 4, 5, 9, 10, 11],
640651
to_s: "1:5,3:4,9:11,10",
@@ -646,6 +657,7 @@ def test_inspect((expected, input, freeze))
646657
data "array", {
647658
input: ["1:5,3:4", 9..11, "10", 99, :*],
648659
elements: [1..5, 9..11, 99, :*],
660+
entries: [1..5, 9..11, 99, :*],
649661
ranges: [1..5, 9..11, 99..99, :*..],
650662
numbers: RangeError,
651663
to_s: "1:5,9:11,99,*",
@@ -657,6 +669,7 @@ def test_inspect((expected, input, freeze))
657669
data "nested array", {
658670
input: [["1:5", [3..4], [[[9..11, "10"], 99], :*]]],
659671
elements: [1..5, 9..11, 99, :*],
672+
entries: [1..5, 9..11, 99, :*],
660673
ranges: [1..5, 9..11, 99..99, :*..],
661674
numbers: RangeError,
662675
to_s: "1:5,9:11,99,*",
@@ -668,6 +681,7 @@ def test_inspect((expected, input, freeze))
668681
data "empty", {
669682
input: nil,
670683
elements: [],
684+
entries: [],
671685
ranges: [],
672686
numbers: [],
673687
to_s: "",
@@ -680,6 +694,26 @@ def test_inspect((expected, input, freeze))
680694
assert_equal data[:elements], SequenceSet.new(data[:input]).elements
681695
end
682696

697+
test "#each_element" do |data|
698+
seqset = SequenceSet.new(data[:input])
699+
array = []
700+
assert_equal seqset, seqset.each_element { array << _1 }
701+
assert_equal data[:elements], array
702+
assert_equal data[:elements], seqset.each_element.to_a
703+
end
704+
705+
test "#entries" do |data|
706+
assert_equal data[:entries], SequenceSet.new(data[:input]).entries
707+
end
708+
709+
test "#each_entry" do |data|
710+
seqset = SequenceSet.new(data[:input])
711+
array = []
712+
assert_equal seqset, seqset.each_entry { array << _1 }
713+
assert_equal data[:entries], array
714+
assert_equal data[:entries], seqset.each_entry.to_a
715+
end
716+
683717
test "#ranges" do |data|
684718
assert_equal data[:ranges], SequenceSet.new(data[:input]).ranges
685719
end

0 commit comments

Comments
 (0)