From 80b721a2c03ec69c4bd9300ca48ee15ee4d717a0 Mon Sep 17 00:00:00 2001 From: nick evans Date: Mon, 28 Apr 2025 18:45:12 -0400 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20`SequenceSet#above`=20and=20`?= =?UTF-8?q?SequenceSet#below`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These are just sugar over `set - (..num)` and `set - (num..)`. They are included because they can sometimes make the code much easier to read than doing set algebra with `-` or `&`. --- lib/net/imap/sequence_set.rb | 56 ++++++++++++++++++++++++++++++ test/net/imap/test_sequence_set.rb | 34 ++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/lib/net/imap/sequence_set.rb b/lib/net/imap/sequence_set.rb index a4b038e2..2fd5b695 100644 --- a/lib/net/imap/sequence_set.rb +++ b/lib/net/imap/sequence_set.rb @@ -248,6 +248,10 @@ class IMAP # +self+ and the other set except those common to both. # - #~ (aliased as #complement): Returns a new set containing all members # that are not in +self+ + # - #above: Return a copy of +self+ which only contains numbers above a + # given number. + # - #below: Return a copy of +self+ which only contains numbers below a + # given value. # - #limit: Returns a copy of +self+ which has replaced * with a # given maximum value and removed all members over that maximum. # @@ -1285,6 +1289,58 @@ def slice_range(range) public + # Returns a copy of +self+ which only contains the numbers above +num+. + # + # Net::IMAP::SequenceSet["5,10:22,50"].above(10) # to_s => "11:22,50" + # Net::IMAP::SequenceSet["5,10:22,50"].above(20) # to_s => "21:22,50 + # Net::IMAP::SequenceSet["5,10:22,50"].above(30) # to_s => "50" + # + # This returns the same result as #intersection with ((num+1)..) + # or #difference with (..num). + # + # Net::IMAP::SequenceSet["5,10:22,50"] & (11..) # to_s => "11:22,50" + # Net::IMAP::SequenceSet["5,10:22,50"] - (..10) # to_s => "11:22,50" + # Net::IMAP::SequenceSet["5,10:22,50"] & (21..) # to_s => "21:22,50" + # Net::IMAP::SequenceSet["5,10:22,50"] - (..20) # to_s => "21:22,50" + # + # Related: #above, #-, #& + def above(num) + NumValidator.valid_nz_number?(num) or + raise ArgumentError, "not a valid sequence set number" + difference(..num) + end + + # Returns a copy of +self+ which only contains numbers below +num+. + # + # Net::IMAP::SequenceSet["5,10:22,50"].below(10) # to_s => "5" + # Net::IMAP::SequenceSet["5,10:22,50"].below(20) # to_s => "5,10:19" + # Net::IMAP::SequenceSet["5,10:22,50"].below(30) # to_s => "5,10:22" + # + # This returns the same result as #intersection with (..(num-1)) + # or #difference with (num..). + # + # Net::IMAP::SequenceSet["5,10:22,50"] & (..9) # to_s => "5" + # Net::IMAP::SequenceSet["5,10:22,50"] - (10..) # to_s => "5" + # Net::IMAP::SequenceSet["5,10:22,50"] & (..19) # to_s => "5,10:19" + # Net::IMAP::SequenceSet["5,10:22,50"] - (20..) # to_s => "5,10:19" + # + # When the set does not contain *, #below is identical to #limit + # with max: num - 1. When the set does contain *, + # #below always drops it from the result. Use #limit when the IMAP + # semantics for * must be enforced. + # + # Net::IMAP::SequenceSet["5,10:22,50"].below(30) # to_s => "5,10:22" + # Net::IMAP::SequenceSet["5,10:22,50"].limit(max: 29) # to_s => "5,10:22" + # Net::IMAP::SequenceSet["5,10:22,*"].below(30) # to_s => "5,10:22" + # Net::IMAP::SequenceSet["5,10:22,*"].limit(max: 29) # to_s => "5,10:22,29" + # + # Related: #above, #-, #&, #limit + def below(num) + NumValidator.valid_nz_number?(num) or + raise ArgumentError, "not a valid sequence set number" + difference(num..) + end + # Returns a frozen SequenceSet with * converted to +max+, numbers # and ranges over +max+ removed, and ranges containing +max+ converted to # end at +max+. diff --git a/test/net/imap/test_sequence_set.rb b/test/net/imap/test_sequence_set.rb index 40cbdc9c..5983a3ca 100644 --- a/test/net/imap/test_sequence_set.rb +++ b/test/net/imap/test_sequence_set.rb @@ -94,6 +94,8 @@ def compare_to_reference_set(nums, set, seqset) data "#xor", {transform: ->{ _1 ^ (1..100) }, } data "#complement", {transform: ->{ ~_1 }, } data "#normalize", {transform: ->{ _1.normalize }, } + data "#above", {transform: ->{ _1.above(22) }, } + data "#below", {transform: ->{ _1.below(22) }, } data "#limit", {transform: ->{ _1.limit(max: 22) }, freeze: :always } data "#limit => empty", {transform: ->{ _1.limit(max: 1) }, freeze: :always } test "transforms keep frozen status" do |data| @@ -387,6 +389,38 @@ def obj.to_sequence_set; 192_168.001_255 end assert_equal 0, SequenceSet["*,1"].find_ordered_index(-1) end + test "#above" do + set = SequenceSet["5,10:22,50"] + assert_equal SequenceSet.empty, set.above(2**32 - 1) + assert_equal SequenceSet.empty, set.above(99) + assert_equal SequenceSet.empty, set.above(50) + assert_equal SequenceSet["50"], set.above(40) + assert_equal SequenceSet["50"], set.above(30) + assert_equal SequenceSet["21:22,50"], set.above(20) + assert_equal SequenceSet["11:22,50"], set.above(10) + assert_equal SequenceSet["5,10:22,50"], set.above(1) + assert_raise ArgumentError do set.above(2**32) end + assert_raise ArgumentError do set.above(0) end + assert_raise ArgumentError do set.above(-1) end + assert_raise ArgumentError do set.above(:*) end + end + + test "#below" do + set = SequenceSet["5,10:22,50"] + assert_equal SequenceSet["5,10:22,50"], set.below(99) + assert_equal SequenceSet["5,10:22"], set.below(50) + assert_equal SequenceSet["5,10:22"], set.below(40) + assert_equal SequenceSet["5,10:22"], set.below(30) + assert_equal SequenceSet["5,10:19"], set.below(20) + assert_equal SequenceSet["5"], set.below(10) + assert_equal SequenceSet.empty, set.below(1) + assert_equal SequenceSet.empty, set.below(1) + assert_raise ArgumentError do set.below(2**32) end + assert_raise ArgumentError do set.below(0) end + assert_raise ArgumentError do set.below(-1) end + assert_raise ArgumentError do set.below(:*) end + end + test "#limit" do set = SequenceSet["1:100,500"] assert_equal [1..99], set.limit(max: 99).ranges