diff --git a/lib/net/imap/sequence_set.rb b/lib/net/imap/sequence_set.rb
index a5dabfc7..27248480 100644
--- a/lib/net/imap/sequence_set.rb
+++ b/lib/net/imap/sequence_set.rb
@@ -256,6 +256,20 @@ class IMAP
# - #disjoint?:
# Returns whether +self+ and a given object have no common elements.
#
+ # Comparison to a number:
+ # - #all_above?:
+ # Returns whether every number in +self+ is greater than a given number.
+ # - #all_below?:
+ # Returns whether every number in +self+ is less than a given number.
+ # - #any_above?:
+ # Returns whether +self+ contains any numbers greater than a given number.
+ # - #any_below?:
+ # Returns whether +self+ contains any numbers less than a given number.
+ # - #none_above?:
+ # Returns whether +self+ contains no numbers greater than a given number.
+ # - #none_below?:
+ # Returns whether +self+ contains no numbers less than a given number.
+ #
# === Methods for Querying
# These methods do not modify +self+.
#
@@ -345,7 +359,7 @@ class IMAP
# - #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.
+ # given number.
# - #limit: Returns a copy of +self+ which has replaced * with a
# given maximum value and removed all members over that maximum.
#
@@ -774,6 +788,225 @@ def disjoint?(other)
empty? || import_runs(other).none? { intersect_run? _1 }
end
+ # :call-seq:
+ # all_above?(number) -> true or false
+ #
+ # Returns whether the set only contains numbers greater than +number+ (not
+ # inclusive of +number+).
+ #
+ # Net::IMAP::SequenceSet["5:8"].all_above?(9) => true
+ # Net::IMAP::SequenceSet["5:8"].all_above?(7) => false
+ # Net::IMAP::SequenceSet["5:8"].all_above?(5) => false
+ #
+ # "*" is evaluated as +UINT32_MAX+:
+ # Net::IMAP::SequenceSet["*"].all_above?(UINT32_MAX - 1) => true
+ # Net::IMAP::SequenceSet["*"].all_above?(UINT32_MAX) => false
+ #
+ # This is roughly equivalent to several other comparisons:
+ #
+ # # Given the following assumptions:
+ # set.empty? => false
+ # set.include_star? => false
+ # number => ^(1...UINT32_MAX)
+ #
+ # # Then these expressions will always return the same result:
+ # set.all_above?(number) => result
+ # set.none_below?(number + 1) => ^result
+ # !set.any_below?(number + 1) => ^result
+ #
+ # set == set.above(number) => ^result
+ # set.disjoint?(..number) => ^result
+ # number < set.min => ^result
+ #
+ # Related: {other comparison methods}[rdoc-ref:SequenceSet@Methods+for+Comparing],
+ # #above
+ def all_above?(number)
+ number = nz_number(number)
+ empty? || number < min(star: UINT32_MAX)
+ end
+
+ # :call-seq:
+ # all_below?(number) -> true or false
+ #
+ # Returns whether the set only contains numbers less than +number+ (not
+ # inclusive of +number+).
+ #
+ # Net::IMAP::SequenceSet["5:8"].all_below?(9) => true
+ # Net::IMAP::SequenceSet["5:8"].all_below?(8) => false
+ # Net::IMAP::SequenceSet["5:8"].all_below?(5) => false
+ #
+ # "*" is evaluated as +UINT32_MAX+:
+ # Net::IMAP::SequenceSet["*"].all_below?(UINT32_MAX) => false
+ #
+ # This is roughly equivalent to several other comparisons:
+ #
+ # # Given the following assumptions:
+ # set.empty? => false
+ # set.include_star? => false
+ # number => ^(2..UINT32_MAX)
+ #
+ # # Then these expressions will always return the same result:
+ # set.all_below?(number) => result
+ # set.none_above?(number - 1) => ^result
+ # !set.any_above?(number - 1) => ^result
+ #
+ # set == set.below(number) => ^result
+ # set.disjoint?(number..) => ^result
+ # set.max < number => ^result
+ #
+ # Related: {other comparison methods}[rdoc-ref:SequenceSet@Methods+for+Comparing],
+ # #below
+ def all_below?(number)
+ number = nz_number(number)
+ empty? || max(star: UINT32_MAX) < number
+ end
+
+ # :call-seq:
+ # any_above?(number) -> true or false
+ #
+ # Returns whether the set contains any numbers greater than +number+ (not
+ # inclusive of +number+).
+ #
+ # Net::IMAP::SequenceSet["5:8"].any_above?(1) => true
+ # Net::IMAP::SequenceSet["5:8"].any_above?(7) => true
+ # Net::IMAP::SequenceSet["5:8"].any_above?(8) => false
+ #
+ # "*" is evaluated as +UINT32_MAX+:
+ # Net::IMAP::SequenceSet["1:*"].any_above?(UINT32_MAX - 1) => true
+ # Net::IMAP::SequenceSet["1:*"].any_above?(UINT32_MAX) => false
+ #
+ # This is roughly equivalent to several other comparisons:
+ #
+ # # Given the following assumptions:
+ # set.empty? => false
+ # set.include_star? => false
+ # number => ^(1...UINT32_MAX)
+ #
+ # # Then these expressions will always return the same result:
+ # set.any_above?(number) => result
+ # !set.none_above?(number) => ^result
+ # !set.all_below?(number + 1) => ^result
+ #
+ # set.cover?(set.above(number)) => ^result
+ # set.intersect?(number + 1..) => ^result
+ # number < set.max => ^result
+ #
+ # Related: {other comparison methods}[rdoc-ref:SequenceSet@Methods+for+Comparing],
+ # #above
+ def any_above?(number)
+ number = nz_number(number)
+ valid? && number < max(star: UINT32_MAX)
+ end
+
+ # :call-seq:
+ # any_below?(number) -> true or false
+ #
+ # Returns whether the set contains any numbers less than +number+ (not
+ # inclusive of +number+).
+ #
+ # Net::IMAP::SequenceSet["5:8"].any_below? 5 => false
+ # Net::IMAP::SequenceSet["5:8"].any_below? 6 => true
+ # Net::IMAP::SequenceSet["5:8"].any_below? 9 => true
+ #
+ # "*" is evaluated as +UINT32_MAX+:
+ # Net::IMAP::SequenceSet["*"].any_below?(UINT32_MAX) => false
+ #
+ # This is roughly equivalent to several other comparisons:
+ #
+ # # Given the following assumptions:
+ # set.empty? => false
+ # set.include_star? => false
+ # number => ^(2..UINT32_MAX)
+ #
+ # # Then these expressions will always return the same result:
+ # set.any_below?(number) => result
+ # !set.none_below?(number) => ^result
+ # !set.all_above?(number - 1) => ^result
+ #
+ # set.cover?(set.below(number)) => ^result
+ # set.intersect?(...number) => ^result
+ # set.min < number => ^result
+ #
+ # Related: {other comparison methods}[rdoc-ref:SequenceSet@Methods+for+Comparing],
+ # #below
+ def any_below?(number)
+ number = nz_number(number)
+ valid? && min(star: UINT32_MAX) < number
+ end
+
+ # :call-seq:
+ # none_above?(number) -> true or false
+ #
+ # Returns whether the set contains no numbers greater than +number+ (not
+ # inclusive of +number+).
+ #
+ # Net::IMAP::SequenceSet["5:8"].none_above?(9) => true
+ # Net::IMAP::SequenceSet["5:8"].none_above?(8) => true
+ # Net::IMAP::SequenceSet["5:8"].none_above?(7) => false
+ #
+ # "*" is evaluated as +UINT32_MAX+:
+ # Net::IMAP::SequenceSet["*"].none_above?(UINT32_MAX - 1) => false
+ # Net::IMAP::SequenceSet["*"].none_above?(UINT32_MAX) => true
+ #
+ # This is roughly equivalent to several other comparisons:
+ #
+ # # Given the following assumptions:
+ # set.empty? => false
+ # set.include_star? => false
+ # number => ^(1...UINT32_MAX)
+ #
+ # # Then these expressions will always return the same result:
+ # set.none_above?(number) => result
+ # !set.any_above?(number) => ^result
+ # set.all_below?(number + 1) => ^result
+ #
+ # set.above(number).empty? => ^result
+ # set.disjoint?(number..) => ^result
+ # set.max <= number => ^result
+ #
+ # Related: {other comparison methods}[rdoc-ref:SequenceSet@Methods+for+Comparing],
+ # #above
+ def none_above?(number)
+ number = nz_number(number)
+ empty? || max(star: UINT32_MAX) <= number
+ end
+
+ # :call-seq:
+ # none_below?(number) -> true or false
+ #
+ # Returns whether the set contains no numbers less than +number+ (not
+ # inclusive of +number+).
+ #
+ # Net::IMAP::SequenceSet["5:8"].none_below?(4) => true
+ # Net::IMAP::SequenceSet["5:8"].none_below?(5) => true
+ # Net::IMAP::SequenceSet["5:8"].none_below?(6) => false
+ #
+ # "*" is evaluated as +UINT32_MAX+:
+ # Net::IMAP::SequenceSet["*"].none_below?(UINT32_MAX) => true
+ #
+ # This is roughly equivalent to several other comparisons:
+ #
+ # # Given the following assumptions:
+ # set.empty? => false
+ # set.include_star? => false
+ # number => ^(2..UINT32_MAX)
+ #
+ # # Then these expressions will always return the same result:
+ # set.none_below?(number) => result
+ # !set.any_below?(number) => ^result
+ # set.all_above?(number - 1) => ^result
+ #
+ # set.above(number).empty? => ^result
+ # set.disjoint?(...number) => ^result
+ # number <= set.min => ^result
+ #
+ # Related: {other comparison methods}[rdoc-ref:SequenceSet@Methods+for+Comparing],
+ # #below
+ def none_below?(number)
+ number = nz_number(number)
+ empty? || number <= min(star: UINT32_MAX)
+ end
+
# :call-seq:
# max(star: :*) => integer or star or nil
# max(count) => SequenceSet
@@ -1560,7 +1793,9 @@ def [](index, length = nil)
# 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, #-, #&
+ # Related: #below,
+ # {other set operations}[rdoc-ref:SequenceSet@Methods+for+Set+Operations],
+ # #all_above?, #any_above?, #none_above?
def above(num)
NumValidator.valid_nz_number?(num) or
raise ArgumentError, "not a valid sequence set number"
@@ -1591,7 +1826,9 @@ def above(num)
# 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
+ # Related: #above,
+ # {other set operations}[rdoc-ref:SequenceSet@Methods+for+Set+Operations],
+ # #all_below?, #any_below?, #none_below?
def below(num)
NumValidator.valid_nz_number?(num) or
raise ArgumentError, "not a valid sequence set number"
diff --git a/test/net/imap/test_sequence_set.rb b/test/net/imap/test_sequence_set.rb
index fd6efa12..f216bb8b 100644
--- a/test/net/imap/test_sequence_set.rb
+++ b/test/net/imap/test_sequence_set.rb
@@ -142,6 +142,25 @@ def compare_to_reference_set(nums, set, seqset)
end
end
+ test "fuzz test: above/below predicates" do
+ 10.times do
+ max = 100
+ size = max / 3
+ set = SequenceSet[Array.new(size) { rand(1..max) }]
+
+ 1.upto(max + 2).each.each_cons(3) do |number, next_number|
+ all_above = set.all_above?(number)
+
+ assert_equal all_above, set.none_below?(next_number)
+ assert_equal all_above, !set.any_below?(next_number)
+
+ none_above = set.none_above?(number)
+ assert_equal none_above, !set.any_above?(number)
+ assert_equal none_above, set.all_below?(next_number)
+ end
+ end
+ end
+
test "#== equality by value (not by identity or representation)" do
assert_equal SequenceSet.new, SequenceSet.new
assert_equal SequenceSet.new("1"), SequenceSet[1]
@@ -746,6 +765,164 @@ def obj.to_sequence_set; 192_168.001_255 end
refute set.disjoint? set
end
+ test "#all_above?" do
+ set = SequenceSet.empty
+ assert_equal true, set.all_above?(1)
+ assert_equal true, set.all_above?("1")
+ assert_equal true, set.all_above?(2**32 - 1)
+ set = SequenceSet.full
+ assert_equal false, set.all_above?(1)
+ assert_equal false, set.all_above?("1")
+ assert_equal false, set.all_above?(2**32 - 2)
+ assert_equal false, set.all_above?(2**32 - 1)
+ set = SequenceSet["*"]
+ assert_equal true, set.all_above?(2**32 - 2)
+ assert_equal false, set.all_above?(2**32 - 1)
+ set = SequenceSet["4:5"]
+ assert_equal true, set.all_above?(1)
+ assert_equal true, set.all_above?(3)
+ assert_equal false, set.all_above?(4)
+ assert_equal false, set.all_above?(6)
+ assert_equal false, set.all_above?(2**32 - 1)
+ assert_raise(DataFormatError) do set.all_above?(0) end
+ assert_raise(DataFormatError) do set.all_above?(-1) end
+ assert_raise(DataFormatError) do set.all_above?(2**32) end
+ assert_raise(DataFormatError) do set.all_above?("*") end
+ assert_raise(DataFormatError) do set.all_above?(:*) end
+ end
+
+ test "#all_below?" do
+ set = SequenceSet.empty
+ assert_equal true, set.all_below?(1)
+ assert_equal true, set.all_below?("1")
+ assert_equal true, set.all_below?(2**32 - 1)
+ set = SequenceSet.full
+ assert_equal false, set.all_below?(1)
+ assert_equal false, set.all_below?("1")
+ assert_equal false, set.all_below?(2**32 - 2)
+ assert_equal false, set.all_below?(2**32 - 1)
+ set = SequenceSet["*"]
+ assert_equal false, set.all_below?(2**32 - 2)
+ assert_equal false, set.all_below?(2**32 - 1)
+ set = SequenceSet["4:5"]
+ assert_equal true, set.all_below?(2**32 - 1)
+ assert_equal true, set.all_below?(6)
+ assert_equal false, set.all_below?(5)
+ assert_equal false, set.all_below?(1)
+ assert_raise(DataFormatError) do set.all_below?(0) end
+ assert_raise(DataFormatError) do set.all_below?(-1) end
+ assert_raise(DataFormatError) do set.all_below?(2**32) end
+ assert_raise(DataFormatError) do set.all_below?("*") end
+ assert_raise(DataFormatError) do set.all_below?(:*) end
+ end
+
+ test "#any_above?" do
+ set = SequenceSet.empty
+ assert_equal false, set.any_above?(1)
+ assert_equal false, set.any_above?("1")
+ assert_equal false, set.any_above?(2**32 - 1)
+ set = SequenceSet.full
+ assert_equal true, set.any_above?(1)
+ assert_equal true, set.any_above?("1")
+ assert_equal true, set.any_above?(2**32 - 2)
+ assert_equal false, set.any_above?(2**32 - 1)
+ set = SequenceSet["*"]
+ assert_equal true, set.any_above?(2**32 - 2)
+ assert_equal false, set.any_above?(2**32 - 1)
+ set = SequenceSet["4:5,*"]
+ assert_equal true, set.any_above?(3)
+ assert_equal true, set.any_above?(4)
+ assert_equal true, set.any_above?(5)
+ assert_equal true, set.any_above?(6)
+ assert_equal false, set.any_above?(2**32 - 1)
+ set = SequenceSet["4:5"]
+ assert_equal true, set.any_above?(3)
+ assert_equal true, set.any_above?(4)
+ assert_equal false, set.any_above?(5)
+ assert_equal false, set.any_above?(6)
+ assert_equal false, set.any_above?(2**32 - 1)
+ assert_raise(DataFormatError) do set.any_above?(0) end
+ assert_raise(DataFormatError) do set.any_above?(-1) end
+ assert_raise(DataFormatError) do set.any_above?(2**32) end
+ assert_raise(DataFormatError) do set.any_above?("*") end
+ assert_raise(DataFormatError) do set.any_above?(:*) end
+ end
+
+ test "#any_below?" do
+ set = SequenceSet.empty
+ assert_equal false, set.any_below?(1)
+ assert_equal false, set.any_below?("1")
+ assert_equal false, set.any_below?(2**32 - 1)
+ set = SequenceSet.full
+ assert_equal false, set.any_below?(1)
+ assert_equal true, set.any_below?(2)
+ assert_equal true, set.any_below?("2")
+ assert_equal true, set.any_below?(2**32 - 1)
+ set = SequenceSet["*"]
+ assert_equal false, set.any_below?(2**32 - 1)
+ set = SequenceSet["3:5"]
+ assert_equal false, set.any_below?(3)
+ assert_equal true, set.any_below?(4)
+ assert_equal true, set.any_below?(5)
+ assert_equal true, set.any_below?(6)
+ assert_raise(DataFormatError) do set.any_below?(0) end
+ assert_raise(DataFormatError) do set.any_below?(-1) end
+ assert_raise(DataFormatError) do set.any_below?(2**32) end
+ assert_raise(DataFormatError) do set.any_below?("*") end
+ assert_raise(DataFormatError) do set.any_below?(:*) end
+ end
+
+ test "#none_above?" do
+ set = SequenceSet.empty
+ assert_equal true, set.none_above?(1)
+ assert_equal true, set.none_above?("1")
+ assert_equal true, set.none_above?(2**32 - 1)
+ set = SequenceSet.full
+ assert_equal false, set.none_above?(1)
+ assert_equal false, set.none_above?("1")
+ assert_equal false, set.none_above?(2**32 - 2)
+ assert_equal true, set.none_above?(2**32 - 1)
+ set = SequenceSet["*"]
+ assert_equal false, set.none_above?(2**32 - 2)
+ assert_equal true, set.none_above?(2**32 - 1)
+ set = SequenceSet["4:5"]
+ assert_equal false, set.none_above?(1)
+ assert_equal false, set.none_above?(3)
+ assert_equal true, set.none_above?(5)
+ assert_equal true, set.none_above?(6)
+ assert_equal true, set.none_above?(2**32 - 1)
+ assert_raise(DataFormatError) do set.none_above?(0) end
+ assert_raise(DataFormatError) do set.none_above?(-1) end
+ assert_raise(DataFormatError) do set.none_above?(2**32) end
+ assert_raise(DataFormatError) do set.none_above?("*") end
+ assert_raise(DataFormatError) do set.none_above?(:*) end
+ end
+
+ test "#none_below?" do
+ set = SequenceSet.empty
+ assert_equal true, set.none_below?(1)
+ assert_equal true, set.none_below?("1")
+ assert_equal true, set.none_below?(2**32 - 1)
+ set = SequenceSet.full
+ assert_equal true, set.none_below?(1)
+ assert_equal true, set.none_below?("1")
+ assert_equal false, set.none_below?(2**32 - 2)
+ assert_equal false, set.none_below?(2**32 - 1)
+ set = SequenceSet["*"]
+ assert_equal true, set.none_below?(2**32 - 2)
+ assert_equal true, set.none_below?(2**32 - 1)
+ set = SequenceSet["4:5"]
+ assert_equal false, set.none_below?(2**32 - 1)
+ assert_equal false, set.none_below?(5)
+ assert_equal true, set.none_below?(4)
+ assert_equal true, set.none_below?(3)
+ assert_raise(DataFormatError) do set.none_below?(0) end
+ assert_raise(DataFormatError) do set.none_below?(-1) end
+ assert_raise(DataFormatError) do set.none_below?(2**32) end
+ assert_raise(DataFormatError) do set.none_below?("*") end
+ assert_raise(DataFormatError) do set.none_below?(:*) end
+ end
+
test "#delete" do
seqset = -> { SequenceSet.new _1 }
assert_equal seqset["1,5"], seqset["1,5"].delete("9")