Skip to content

✨ Add Net::IMAP::SequenceSet() coercion method #490

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions lib/net/imap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -809,6 +809,22 @@ class IMAP < Protocol
include SSL
end

# :call-seq:
# Net::IMAP::SequenceSet(set = nil) -> SequenceSet
#
# Coerces +set+ into a SequenceSet, using either SequenceSet.try_convert or
# SequenceSet.new.
#
# * When +set+ is a SequenceSet, that same set is returned.
# * When +set+ responds to +to_sequence_set+, +set.to_sequence_set+ is
# returned.
# * Otherwise, returns the result from calling SequenceSet.new with +set+.
#
# Related: SequenceSet.try_convert, SequenceSet.new, SequenceSet::[]
def self.SequenceSet(set = nil)
SequenceSet.try_convert(set) || SequenceSet.new(set)
end

# Returns the global Config object
def self.config; Config.global end

Expand Down
31 changes: 28 additions & 3 deletions lib/net/imap/sequence_set.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,28 @@ class IMAP
# copy == input #=> false, different set membership
# copy.eql? input #=> false, different string value
#
# Use Net::IMAP::SequenceSet() to coerce a single (optional) input.
# A SequenceSet input is returned without duplication, even when frozen.
#
# set = Net::IMAP::SequenceSet()
# set.string #=> nil
# set.frozen? #=> false
#
# # String order is preserved
# set = Net::IMAP::SequenceSet("1,2,3:7,5,6:10,2048,1024")
# set.valid_string #=> "1,2,3:7,5,6:10,2048,1024"
# set.frozen? #=> false
#
# # Other inputs are normalized
# set = Net::IMAP::SequenceSet([1, 2, [3..7, 5], 6..10, 2048, 1024])
# set.valid_string #=> "1:10,55,1024:2048"
# set.frozen? #=> false
#
# unfrozen = set
# frozen = set.dup.freeze
# unfrozen.equal? Net::IMAP::SequenceSet(unfrozen) #=> true
# frozen.equal? Net::IMAP::SequenceSet(frozen) #=> true
#
# Use ::[] to coerce one or more arguments into a valid frozen SequenceSet.
# A valid frozen SequenceSet is returned directly, without allocating a new
# object. ::[] will not create an invalid (empty) set.
Expand All @@ -95,7 +117,7 @@ class IMAP
#
# Objects which respond to +to_sequence_set+ (such as SearchResult and
# ThreadMember) can be coerced to a SequenceSet with ::new, ::try_convert,
# or ::[].
# ::[], or Net::IMAP::SequenceSet.
#
# search = imap.uid_search(["SUBJECT", "hello", "NOT", "SEEN"])
# seqset = Net::IMAP::SequenceSet(search) - already_fetched
Expand Down Expand Up @@ -203,6 +225,7 @@ class IMAP
# * ::new: Creates a new mutable sequence set, which may be empty (invalid).
# * ::try_convert: Calls +to_sequence_set+ on an object and verifies that
# the result is a SequenceSet.
# * Net::IMAP::SequenceSet(): Coerce an input using ::try_convert or ::new.
# * ::empty: Returns a frozen empty (invalid) SequenceSet.
# * ::full: Returns a frozen SequenceSet containing every possible number.
#
Expand Down Expand Up @@ -387,7 +410,7 @@ class << self
#
# Use ::new to create a mutable or empty SequenceSet.
#
# Related: ::new, ::try_convert
# Related: ::new, Net::IMAP::SequenceSet(), ::try_convert
def [](first, *rest)
if rest.empty?
if first.is_a?(SequenceSet) && first.frozen? && first.valid?
Expand All @@ -410,7 +433,7 @@ def [](first, *rest)
# If +obj.to_sequence_set+ doesn't return a SequenceSet, an exception is
# raised.
#
# Related: ::new, ::[]
# Related: Net::IMAP::SequenceSet(), ::new, ::[]
def try_convert(obj)
return obj if obj.is_a?(SequenceSet)
return nil unless obj.respond_to?(:to_sequence_set)
Expand Down Expand Up @@ -486,6 +509,8 @@ def full; FULL end
# * ::[] returns a frozen validated (non-empty) SequenceSet, without
# allocating a new object when the input is already a valid frozen
# SequenceSet.
# * Net::IMAP::SequenceSet() coerces an input to SequenceSet, without
# allocating a new object when the input is already a SequenceSet.
# * ::try_convert calls +to_sequence_set+ on inputs that support it and
# returns +nil+ for inputs that don't.
# * ::empty and ::full both return frozen singleton sets which can be
Expand Down
30 changes: 30 additions & 0 deletions test/net/imap/test_sequence_set.rb
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,36 @@ def obj.to_sequence_set; 192_168.001_255 end
assert_raise DataFormatError do SequenceSet.try_convert(obj) end
end

test "Net::IMAP::SequenceSet(set)" do
assert_equal SequenceSet.empty, Net::IMAP::SequenceSet()
assert_equal SequenceSet.empty, Net::IMAP::SequenceSet(nil)
assert_equal SequenceSet.empty, Net::IMAP::SequenceSet([])
assert_equal SequenceSet.empty, Net::IMAP::SequenceSet([[]])
assert_equal SequenceSet.empty, Net::IMAP::SequenceSet("")
assert_equal SequenceSet[123], Net::IMAP::SequenceSet(123)
assert_equal SequenceSet[12..34], Net::IMAP::SequenceSet(12..34)
assert_equal SequenceSet[12..34], Net::IMAP::SequenceSet("12:34")

refute Net::IMAP::SequenceSet("").frozen?

assert_raise DataFormatError do Net::IMAP::SequenceSet(Object.new) end

set = SequenceSet[123]
assert_same set, Net::IMAP::SequenceSet(set)

set = SequenceSet.new(123)
assert_same set, Net::IMAP::SequenceSet(set)

obj = Object.new
set = SequenceSet[192, 168, 1, 255]
obj.define_singleton_method(:to_sequence_set) { set }
assert_same set, Net::IMAP::SequenceSet(obj)

obj = Object.new
def obj.to_sequence_set; 192_168.001_255 end
assert_raise DataFormatError do Net::IMAP::SequenceSet(obj) end
end

test "#at(non-negative index)" do
assert_nil SequenceSet.empty.at(0)
assert_equal 1, SequenceSet[1..].at(0)
Expand Down