class Net::IMAP::SequenceSet

An IMAP sequence set is a set of message sequence numbers or unique identifier numbers (“UIDs”). It contains numbers and ranges of numbers. The numbers are all non-zero unsigned 32-bit integers and one special value ("*") that represents the largest value in the mailbox.

Certain types of IMAP responses will contain a SequenceSet, for example the data for a "MODIFIED" ResponseCode. Some IMAP commands may receive a SequenceSet as an argument, for example IMAP#search, IMAP#fetch, and IMAP#store.

Creating sequence sets

SequenceSet.new with no arguments creates an empty sequence set. Note that an empty sequence set is invalid in the IMAP grammar.

set = Net::IMAP::SequenceSet.new
set.empty?        #=> true
set.valid?        #=> false
set.valid_string  #!> raises DataFormatError
set << 1..10
set.empty?        #=> false
set.valid?        #=> true
set.valid_string  #=> "1:10"

SequenceSet.new may receive a single optional argument: a non-zero 32 bit unsigned integer, a range, a sequence-set formatted string, another sequence set, a Set (containing only numbers or *), or an Array containing any of these (array inputs may be nested).

set = Net::IMAP::SequenceSet.new(1)
set.valid_string  #=> "1"
set = Net::IMAP::SequenceSet.new(1..100)
set.valid_string  #=> "1:100"
set = Net::IMAP::SequenceSet.new(1...100)
set.valid_string  #=> "1:99"
set = Net::IMAP::SequenceSet.new([1, 2, 5..])
set.valid_string  #=> "1:2,5:*"
set = Net::IMAP::SequenceSet.new("1,2,3:7,5,6:10,2048,1024")
set.valid_string  #=> "1,2,3:7,5,6:10,2048,1024"
set = Net::IMAP::SequenceSet.new(1, 2, 3..7, 5, 6..10, 2048, 1024)
set.valid_string  #=> "1:10,55,1024:2048"

Use ::[] with one or more arguments to create a frozen SequenceSet. An invalid (empty) set cannot be created with ::[].

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 = Net::IMAP::SequenceSet[1, 2, [3..7, 5], 6..10, 2048, 1024]
set.valid_string  #=> "1:10,55,1024:2048"

Ordered and Normalized sets

Sometimes the order of the set’s members is significant, such as with the ESORT, CONTEXT=SORT, and UIDPLUS extensions. So, when a sequence set is created by the parser or with a single string value, that string representation is preserved.

Internally, SequenceSet stores a normalized representation which sorts all entries, de-duplicates numbers, and coalesces adjacent or overlapping ranges. Most methods use this normalized representation to achieve O(lg n) porformance. Use entries or each_entry to enumerate the set in its original order.

Most modification methods convert string to its normalized form. To preserve string order while modifying a set, use append, string=, or replace.

Using *

IMAP sequence sets may contain a special value "*", which represents the largest number in use. From seq-number in RFC9051 §9:

In the case of message sequence numbers, it is the number of messages in a non-empty mailbox. In the case of unique identifiers, it is the unique identifier of the last message in the mailbox or, if the mailbox is empty, the mailbox’s current UIDNEXT value.

When creating a SequenceSet, * may be input as -1, "*", :*, an endless range, or a range ending in -1. When converting to elements, ranges, or numbers, it will output as either :* or an endless range. For example:

Net::IMAP::SequenceSet["1,3,*"].to_a      #=> [1, 3, :*]
Net::IMAP::SequenceSet["1,234:*"].to_a    #=> [1, 234..]
Net::IMAP::SequenceSet[1234..-1].to_a     #=> [1234..]
Net::IMAP::SequenceSet[1234..].to_a       #=> [1234..]

Net::IMAP::SequenceSet[1234..].to_s       #=> "1234:*"
Net::IMAP::SequenceSet[1234..-1].to_s     #=> "1234:*"

Use limit to convert "*" to a maximum value. When a range includes "*", the maximum value will always be matched:

Net::IMAP::SequenceSet["9999:*"].limit(max: 25)
#=> Net::IMAP::SequenceSet["25"]

Surprising * behavior

When a set includes *, some methods may have surprising behavior.

For example, complement treats * as its own number. This way, the intersection of a set and its complement will always be empty. This is not how an IMAP server interprets the set: it will convert * to either the number of messages in the mailbox or UIDNEXT, as appropriate. And there will be overlap between a set and its complement after limit is applied to each:

~Net::IMAP::SequenceSet["*"]  == Net::IMAP::SequenceSet[1..(2**32-1)]
~Net::IMAP::SequenceSet[1..5] == Net::IMAP::SequenceSet["6:*"]

set = Net::IMAP::SequenceSet[1..5]
(set & ~set).empty? => true

(set.limit(max: 4) & (~set).limit(max: 4)).to_a => [4]

When counting the number of numbers in a set, * will be counted except when UINT32_MAX is also in the set:

UINT32_MAX = 2**32 - 1
Net::IMAP::SequenceSet["*"].count                   => 1
Net::IMAP::SequenceSet[1..UINT32_MAX - 1, :*].count => UINT32_MAX

Net::IMAP::SequenceSet["1:*"].count                 => UINT32_MAX
Net::IMAP::SequenceSet[UINT32_MAX, :*].count        => 1
Net::IMAP::SequenceSet[UINT32_MAX..].count          => 1

What’s here?

SequenceSet provides methods for:

Methods for Creating a SequenceSet

Methods for Comparing

Comparison to another SequenceSet:

Comparison to objects which are convertible to SequenceSet:

Methods for Querying

These methods do not modify self.

Set membership:

Minimum and maximum value elements:

Accessing value by (normalized) offset:

Set cardinality:

Methods for Iterating

Methods for Set Operations

These methods do not modify self.

Methods for Assigning

These methods add or replace elements in self.

Methods for Deleting

These methods remove elements from self.

Methods for IMAP String Formatting