Class: Net::IMAP::SequenceSet

Inherits:
Object
  • Object
show all
Defined in:
lib/net/imap/sequence_set.rb

Overview

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 may receive a single optional argument: a non-zero 32 bit unsigned integer, a range, a sequence-set formatted string, another SequenceSet, 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"

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"

Using SequenceSet.new with another SequenceSet input behaves the same as calling #dup on the other set. The input’s #string will be preserved.

input = Net::IMAP::SequenceSet.new("1,2,3:7,5,6:10,2048,1024")
copy  = Net::IMAP::SequenceSet.new(input)
input.valid_string  #=> "1,2,3:7,5,6:10,2048,1024"
copy.valid_string   #=> "1,2,3:7,5,6:10,2048,1024"
copy2 = input.dup   # same as calling new with a SequenceSet input
copy ==     input   #=> true,  same set membership
copy.eql?   input   #=> true,  same string value
copy.equal? input   #=> false, different objects

copy.normalize!
copy.valid_string   #=> "1:10,1024,2048"
copy ==   input     #=> true,  same set membership
copy.eql? input     #=> false, different string value

copy << 999
copy.valid_string   #=> "1:10,999,1024,2048"
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,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.

Net::IMAP::SequenceSet[]     #!> raises ArgumentError
Net::IMAP::SequenceSet[nil]  #!> raises DataFormatError
Net::IMAP::SequenceSet[""]   #!> raises DataFormatError

# 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?       #=> true

# Other inputs are normalized
set = Net::IMAP::SequenceSet[1, 2, [3..7, 5], 6..10, 2048, 1024]
set.valid_string  #=> "1:10,1024,2048"
set.frozen?       #=> true

frozen   = set
unfrozen = set.dup
frozen.equal?   Net::IMAP::SequenceSet[frozen]    #=> true
unfrozen.equal? Net::IMAP::SequenceSet[unfrozen]  #=> false

Objects which respond to to_sequence_set (such as SearchResult and ThreadMember) can be coerced to a SequenceSet with ::new, ::try_convert, ::[], or Net::IMAP::SequenceSet.

search = imap.uid_search(["SUBJECT", "hello", "NOT", "SEEN"])
seqset = Net::IMAP::SequenceSet(search) - already_fetched
fetch  = imap.uid_fetch(seqset, "FAST")

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 from a single string (such as by the parser), that #string representation is preserved. Assigning a string with #string= or #replace will also preserve that string. Use #each_entry, #entries, or #each_ordered_number to enumerate the entries in their #string order. Hash equality (using #eql?) is based on the string representation.

Internally, SequenceSet uses a normalized uint32 set representation which sorts and de-duplicates all numbers and coalesces adjacent or overlapping entries. Many methods use this sorted set representation for O(lg n) searches. Use #each_element, #elements, #each_range, #ranges, #each_number, or #numbers to enumerate the set in sorted order. Basic object equality (using #==) is based on set membership, without regard to #entry order or #string normalization.

Most modification methods reset #string to its #normalized form, so that #entries and #elements are identical. Use #append to preserve #entries order while modifying a set.

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. And * is sorted as greater than any other number in the set. This is not how an IMAP server interprets the set: it will convert * to the number of messages in the mailbox, the UID of the last message in the mailbox, or UIDNEXT, as appropriate. Several methods have an argument for how * should be interpreted.

But, for example, this means that there may 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

  • ::[]: Creates a validated frozen sequence set from one or more inputs.

  • ::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.

Methods for Comparing

Comparison to another SequenceSet:

  • #==: Returns whether a given set contains the same numbers as self.

  • #eql?: Returns whether a given set uses the same #string as self.

Comparison to objects which are convertible to SequenceSet:

  • #===: Returns whether a given object is fully contained within self, or nil if the object cannot be converted to a compatible type.

  • #cover?: Returns whether a given object is fully contained within self.

  • #intersect? (aliased as #overlap?): Returns whether self and a given object have any common elements.

  • #disjoint?: Returns whether self and a given object have no common elements.

Methods for Querying

These methods do not modify self.

Set membership:

  • #include? (aliased as #member?): Returns whether a given element is contained by the set.

  • #include_star?: Returns whether the set contains *.

Minimum and maximum value elements:

  • #min: Returns one or more of the lowest numbers in the set.

  • #max: Returns one or more of the highest numbers in the set.

  • #minmax: Returns the lowest and highest numbers in the set.

Accessing value by offset in sorted set:

  • #[] (aliased as #slice): Returns the number or consecutive subset at a given offset or range of offsets in the sorted set.

  • #at: Returns the number at a given offset in the sorted set.

  • #find_index: Returns the given number’s offset in the sorted set.

Accessing value by offset in ordered entries

  • #ordered_at: Returns the number at a given offset in the ordered entries.

  • #find_ordered_index: Returns the index of the given number’s first occurrence in entries.

Set cardinality:

  • #count (aliased as #size): Returns the count of numbers in the set. Duplicated numbers are not counted.

  • #empty?: Returns whether the set has no members. IMAP syntax does not allow empty sequence sets.

  • #valid?: Returns whether the set has any members.

  • #full?: Returns whether the set contains every possible value, including *.

Denormalized properties:

  • #has_duplicates?: Returns whether the ordered entries repeat any numbers.

  • #count_duplicates: Returns the count of repeated numbers in the ordered entries.

  • #count_with_duplicates: Returns the count of numbers in the ordered entries, including any repeated numbers.

Methods for Iterating

Normalized (sorted and coalesced):

  • #each_element: Yields each number and range in the set, sorted and coalesced, and returns self.

  • #elements (aliased as #to_a): Returns an Array of every number and range in the set, sorted and coalesced.

  • #each_range: Yields each element in the set as a Range and returns self.

  • #ranges: Returns an Array of every element in the set, converting numbers into ranges of a single value.

  • #each_number: Yields each number in the set and returns self.

  • #numbers: Returns an Array with every number in the set, expanding ranges into all of their contained numbers.

  • #to_set: Returns a Set containing all of the #numbers in the set.

Order preserving:

  • #each_entry: Yields each number and range in the set, unsorted and without deduplicating numbers or coalescing ranges, and returns self.

  • #entries: Returns an Array of every number and range in the set, unsorted and without deduplicating numbers or coalescing ranges.

  • #each_ordered_number: Yields each number in the ordered entries and returns self.

Methods for Set Operations

These methods do not modify self.

  • #| (aliased as #union and #+): Returns a new set combining all members from self with all members from the other set.

  • #& (aliased as #intersection): Returns a new set containing all members common to self and the other set.

  • #- (aliased as #difference): Returns a copy of self with all members in the other set removed.

  • #^ (aliased as #xor): Returns a new set containing all members from 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.

Methods for Assigning

These methods add or replace elements in self.

Normalized (sorted and coalesced):

These methods always update #string to be fully sorted and coalesced.

  • #add (aliased as #<<): Adds a given element to the set; returns self.

  • #add?: If the given element is not fully included the set, adds it and returns self; otherwise, returns nil.

  • #merge: Adds all members of the given sets into this set; returns self.

  • #complement!: Replaces the contents of the set with its own #complement.

Order preserving:

These methods may cause #string to not be sorted or coalesced.

  • #append: Adds the given entry to the set, appending it to the existing string, and returns self.

  • #string=: Assigns a new #string value and replaces #elements to match.

  • #replace: Replaces the contents of the set with the contents of a given object.

Methods for Deleting

These methods remove elements from self, and update #string to be fully sorted and coalesced.

  • #clear: Removes all elements in the set; returns self.

  • #delete: Removes a given element from the set; returns self.

  • #delete?: If the given element is included in the set, removes it and returns it; otherwise, returns nil.

  • #delete_at: Removes the number at a given offset.

  • #slice!: Removes the number or consecutive numbers at a given offset or range of offsets.

  • #subtract: Removes all members of the given sets from this set; returns self.

  • #limit!: Replaces * with a given maximum value and removes all members over that maximum; returns self.

Methods for IMAP String Formatting

  • #to_s: Returns the sequence-set string, or an empty string when the set is empty.

  • #string: Returns the sequence-set string, or nil when empty.

  • #valid_string: Returns the sequence-set string, or raises DataFormatError when the set is empty.

  • #normalized_string: Returns a sequence-set string with its elements sorted and coalesced, or nil when the set is empty.

  • #normalize: Returns a new set with this set’s normalized sequence-set representation.

  • #normalize!: Updates #string to its normalized sequence-set representation and returns self.

Constant Summary collapse

UINT32_MAX =

The largest possible non-zero unsigned 32-bit integer

2**32 - 1

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(input = nil) ⇒ SequenceSet

Create a new SequenceSet object from input, which may be another SequenceSet, an IMAP formatted sequence-set string, a non-zero 32 bit unsigned integer, a range, :*, a Set of numbers or *, an object that responds to to_sequence_set (such as SearchResult) or an Array 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,1024,2048"

With no arguments (or nil) 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"

When input is a SequenceSet, ::new behaves the same as calling #dup on that other set. The input’s #string will be preserved.

input = Net::IMAP::SequenceSet.new("1,2,3:7,5,6:10,2048,1024")
copy  = Net::IMAP::SequenceSet.new(input)
input.valid_string  #=> "1,2,3:7,5,6:10,2048,1024"
copy.valid_string   #=> "1,2,3:7,5,6:10,2048,1024"
copy2 = input.dup   # same as calling new with a SequenceSet input
copy ==     input   #=> true,  same set membership
copy.eql?   input   #=> true,  same string value
copy.equal? input   #=> false, different objects

copy.normalize!
copy.valid_string   #=> "1:10,1024,2048"
copy ==   input     #=> true,  same set membership
copy.eql? input     #=> false, different string value

copy << 999
copy.valid_string   #=> "1:10,999,1024,2048"
copy ==   input     #=> false, different set membership
copy.eql? input     #=> false, different string value

Alternative set creation methods

  • ::[] 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 combined with set operations (#|, #&, #^, #-, etc) to make new sets.

See SequenceSet@Creating+sequence+sets.



534
# File 'lib/net/imap/sequence_set.rb', line 534

def initialize(input = nil) input ? replace(input) : clear end

Class Method Details

.[](first, *rest) ⇒ Object

:call-seq:

SequenceSet[*inputs] -> valid frozen sequence set

Returns a frozen SequenceSet, constructed from inputs.

When only a single valid frozen SequenceSet is given, that same set is returned.

An empty SequenceSet is invalid and will raise a DataFormatError.

Use ::new to create a mutable or empty SequenceSet.

Related: ::new, Net::IMAP::SequenceSet(), ::try_convert



431
432
433
434
435
436
437
438
# File 'lib/net/imap/sequence_set.rb', line 431

def [](first, *rest)
  if rest.empty?
    set = try_convert(first)&.validate
    set&.frozen? ? set : (set&.dup || new(first).validate).freeze
  else
    new(first).merge(*rest).validate.freeze
  end
end

.emptyObject

Returns a frozen empty set singleton. Note that valid IMAP sequence sets cannot be empty, so this set is invalid.



461
# File 'lib/net/imap/sequence_set.rb', line 461

def empty; EMPTY end

.fullObject

Returns a frozen full set singleton: "1:*"



464
# File 'lib/net/imap/sequence_set.rb', line 464

def full;  FULL end

.try_convert(obj) ⇒ Object

:call-seq:

SequenceSet.try_convert(obj) -> sequence set or nil

If obj is a SequenceSet, returns obj. If obj responds_to to_sequence_set, calls obj.to_sequence_set and returns the result. Otherwise returns nil.

If obj.to_sequence_set doesn’t return a SequenceSet or nil, an exception is raised.

Related: Net::IMAP::SequenceSet(), ::new, ::[]

Raises:



451
452
453
454
455
456
457
# File 'lib/net/imap/sequence_set.rb', line 451

def try_convert(obj)
  return obj if obj.is_a?(SequenceSet)
  return nil unless obj.respond_to?(:to_sequence_set)
  return nil unless obj = obj.to_sequence_set
  return obj if obj.is_a?(SequenceSet)
  raise DataFormatError, "invalid object returned from to_sequence_set"
end

Instance Method Details

#&(other) ⇒ Object Also known as: intersection

:call-seq:

self & other        -> sequence set
intersection(other) -> sequence set

Returns a new sequence set containing only the numbers common to this set and other.

other may be any object that would be accepted by ::new.

Net::IMAP::SequenceSet[1..5] & [2, 4, 6]
#=> Net::IMAP::SequenceSet["2,4"]

Related: #intersect?, #|, #-, #^, #~

Set identities

lhs & rhs is equivalent to:

  • rhs & lhs (commutative)

  • ~(~lhs | ~rhs) (De Morgan’s Law)

  • lhs - ~rhs

  • lhs - (lhs - rhs)

  • lhs - (lhs ^ rhs)

  • lhs ^ (lhs - rhs)



876
877
878
# File 'lib/net/imap/sequence_set.rb', line 876

def &(other)
  remain_frozen dup.subtract SequenceSet.new(other).complement!
end

#-(other) ⇒ Object Also known as: difference

:call-seq:

self - other      -> sequence set
difference(other) -> sequence set

Returns a new sequence set built by duplicating this set and removing every number that appears in other.

other may be any object that would be accepted by ::new.

Net::IMAP::SequenceSet[1..5] - 2 - 4 - 6
#=> Net::IMAP::SequenceSet["1,3,5"]

Related: #subtract, #|, #&, #^, #~

Set identities

lhs - rhs is equivalent to:

  • ~rhs - ~lhs

  • lhs & ~rhs

  • ~(~lhs | rhs)

  • lhs & (lhs ^ rhs)

  • lhs ^ (lhs & rhs)

  • rhs ^ (lhs | rhs)



850
# File 'lib/net/imap/sequence_set.rb', line 850

def -(other) remain_frozen dup.subtract other end

#==(other) ⇒ Object

:call-seq: self == other -> true or false

Returns true when the other SequenceSet represents the same message identifiers. Encoding difference—such as order, overlaps, or duplicates—are ignored.

Net::IMAP::SequenceSet["1:3"]   == Net::IMAP::SequenceSet["1:3"]
#=> true
Net::IMAP::SequenceSet["1,2,3"] == Net::IMAP::SequenceSet["1:3"]
#=> true
Net::IMAP::SequenceSet["1,3"]   == Net::IMAP::SequenceSet["3,1"]
#=> true
Net::IMAP::SequenceSet["9,1:*"] == Net::IMAP::SequenceSet["1:*"]
#=> true

Related: #eql?, #normalize



642
643
644
645
# File 'lib/net/imap/sequence_set.rb', line 642

def ==(other)
  self.class == other.class &&
    (to_s == other.to_s || tuples == other.tuples)
end

#===(other) ⇒ Object

:call-seq: self === other -> true | false | nil

Returns whether other is contained within the set. other may be any object that would be accepted by ::new. Returns nil if StandardError is raised while converting other to a comparable type.

Related: #cover?, #include?, #include_star?



673
674
675
676
677
# File 'lib/net/imap/sequence_set.rb', line 673

def ===(other)
  cover?(other)
rescue
  nil
end

#[](index, length = nil) ⇒ Object Also known as: slice

:call-seq:

seqset[index]         -> integer or :* or nil
slice(index)          -> integer or :* or nil
seqset[start, length] -> sequence set or nil
slice(start, length)  -> sequence set or nil
seqset[range]         -> sequence set or nil
slice(range)          -> sequence set or nil

Returns a number or a subset from the sorted set, without modifying the set.

When an Integer argument index is given, the number at offset index in the sorted set is returned:

set = Net::IMAP::SequenceSet["10:15,20:23,26"]
set[0]   #=> 10
set[5]   #=> 15
set[10]  #=> 26

If index is negative, it counts relative to the end of the sorted set:

set = Net::IMAP::SequenceSet["10:15,20:23,26"]
set[-1]  #=> 26
set[-3]  #=> 22
set[-6]  #=> 15

If index is out of range, nil is returned.

set = Net::IMAP::SequenceSet["10:15,20:23,26"]
set[11]  #=> nil
set[-12] #=> nil

The result is based on the sorted and de-duplicated set, not on the ordered #entries in #string.

set = Net::IMAP::SequenceSet["12,20:23,11:16,21"]
set[0]   #=> 11
set[-1]  #=> 23

Related: #at



1464
1465
1466
1467
1468
1469
# File 'lib/net/imap/sequence_set.rb', line 1464

def [](index, length = nil)
  if    length              then slice_length(index, length)
  elsif index.is_a?(Range)  then slice_range(index)
  else                           at(index)
  end
end

#^(other) ⇒ Object Also known as: xor

:call-seq:

self ^ other -> sequence set
xor(other)   -> sequence set

Returns a new sequence set containing numbers that are exclusive between this set and other.

other may be any object that would be accepted by ::new.

Net::IMAP::SequenceSet[1..5] ^ [2, 4, 6]
#=> Net::IMAP::SequenceSet["1,3,5:6"]

Related: #|, #&, #-, #~

Set identities

lhs ^ rhs is equivalent to:

  • rhs ^ lhs (commutative)

  • ~lhs ^ ~rhs

  • (lhs | rhs) - (lhs & rhs)

  • (lhs - rhs) | (rhs - lhs)

  • (lhs ^ other) ^ (other ^ rhs)



903
# File 'lib/net/imap/sequence_set.rb', line 903

def ^(other) remain_frozen (dup | other).subtract(self & other) end

#above(num) ⇒ Object

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, #-, #&



1519
1520
1521
1522
1523
# File 'lib/net/imap/sequence_set.rb', line 1519

def above(num)
  NumValidator.valid_nz_number?(num) or
    raise ArgumentError, "not a valid sequence set number"
  difference(..num)
end

#add(element) ⇒ Object Also known as: <<

:call-seq:

add(element)   -> self
self << other -> self

Adds a range or number to the set and returns self.

#string will be regenerated. Use #merge to add many elements at once.

Use #append to append new elements to #string. See SequenceSet@Ordered+and+Normalized+sets.

Related: #add?, #merge, #union, #append



941
942
943
944
945
# File 'lib/net/imap/sequence_set.rb', line 941

def add(element)
  modifying! # short-circuit before input_to_tuple
  tuple_add input_to_tuple element
  normalize!
end

#add?(element) ⇒ Boolean

:call-seq: add?(element) -> self or nil

Adds a range or number to the set and returns self. Returns nil when the element is already included in the set.

#string will be regenerated. Use #merge to add many elements at once.

Related: #add, #merge, #union, #include?

Returns:

  • (Boolean)


974
975
976
977
# File 'lib/net/imap/sequence_set.rb', line 974

def add?(element)
  modifying! # short-circuit before include?
  add element unless include? element
end

#append(entry) ⇒ Object

Adds a range or number to the set and returns self.

Unlike #add, #merge, or #union, the new value is appended to #string. This may result in a #string which has duplicates or is out-of-order.

See SequenceSet@Ordered+and+Normalized+sets.

Related: #add, #merge, #union



956
957
958
959
960
961
962
963
964
# File 'lib/net/imap/sequence_set.rb', line 956

def append(entry)
  modifying! # short-circuit before input_to_tuple
  tuple = input_to_tuple entry
  entry = tuple_to_str tuple
  string unless empty? # write @string before tuple_add
  tuple_add tuple
  @string = -(@string ? "#{@string},#{entry}" : entry)
  self
end

#at(index) ⇒ Object

:call-seq: at(index) -> integer or nil

Returns the number at the given index in the sorted set, without modifying the set.

index is interpreted the same as in #[], except that #at only allows a single integer argument.

Related: #[], #slice, #ordered_at



1394
1395
1396
# File 'lib/net/imap/sequence_set.rb', line 1394

def at(index)
  lookup_number_by_tuple_index(tuples, index)
end

#below(num) ⇒ Object

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



1550
1551
1552
1553
1554
# File 'lib/net/imap/sequence_set.rb', line 1550

def below(num)
  NumValidator.valid_nz_number?(num) or
    raise ArgumentError, "not a valid sequence set number"
  difference(num..)
end

#clearObject

Removes all elements and returns self.



537
538
539
540
541
# File 'lib/net/imap/sequence_set.rb', line 537

def clear
  modifying! # redundant check, to normalize the error message for JRuby
  @tuples, @string = [], nil
  self
end

#complement!Object

:call-seq: complement! -> self

Converts the SequenceSet to its own #complement. It will contain all possible values except for those currently in the set.

Related: #complement



1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
# File 'lib/net/imap/sequence_set.rb', line 1602

def complement!
  modifying! # short-circuit, and normalize the error message for JRuby
  return replace(self.class.full) if empty?
  return clear                    if full?
  flat = @tuples.flat_map { [_1 - 1, _2 + 1] }
  if flat.first < 1         then flat.shift else flat.unshift 1        end
  if STAR_INT   < flat.last then flat.pop   else flat.push    STAR_INT end
  @tuples = flat.each_slice(2).to_a
  normalize!
end

#countObject Also known as: size

Returns the count of #numbers in the set.

* will be counted as 2**32 - 1 (the maximum 32-bit unsigned integer value).

Related: #count_with_duplicates



1289
1290
1291
1292
# File 'lib/net/imap/sequence_set.rb', line 1289

def count
  @tuples.sum(@tuples.count) { _2 - _1 } +
    (include_star? && include?(UINT32_MAX) ? -1 : 0)
end

#count_duplicatesObject

Returns the count of repeated numbers in the ordered #entries, the difference between #count_with_duplicates and #count.

When #string is normalized, this is zero.

Related: #entries, #count_with_duplicates, #has_duplicates?



1318
1319
1320
1321
# File 'lib/net/imap/sequence_set.rb', line 1318

def count_duplicates
  return 0 unless @string
  count_with_duplicates - count
end

#count_with_duplicatesObject

Returns the count of numbers in the ordered #entries, including any repeated numbers.

* will be counted as 2**32 - 1 (the maximum 32-bit unsigned integer value).

When #string is normalized, this behaves the same as #count.

Related: #entries, #count_duplicates, #has_duplicates?



1305
1306
1307
1308
1309
1310
# File 'lib/net/imap/sequence_set.rb', line 1305

def count_with_duplicates
  return count unless @string
  each_entry_tuple.sum {|min, max|
    max - min + ((max == STAR_INT && min != STAR_INT) ? 0 : 1)
  }
end

#cover?(other) ⇒ Boolean

:call-seq: cover?(other) -> true | false | nil

Returns whether other is contained within the set. other may be any object that would be accepted by ::new.

Related: #===, #include?, #include_star?, #intersect?

Returns:

  • (Boolean)


685
# File 'lib/net/imap/sequence_set.rb', line 685

def cover?(other) input_to_tuples(other).none? { !include_tuple?(_1) } end

#deconstructObject

Returns an array with #normalized_string when valid and an empty array otherwise.



587
# File 'lib/net/imap/sequence_set.rb', line 587

def deconstruct; valid? ? [normalized_string] : [] end

#delete(element) ⇒ Object

:call-seq: delete(element) -> self

Deletes the given range or number from the set and returns self.

#string will be regenerated after deletion. Use #subtract to remove many elements at once.

Related: #delete?, #delete_at, #subtract, #difference



987
988
989
990
991
# File 'lib/net/imap/sequence_set.rb', line 987

def delete(element)
  modifying! # short-circuit before input_to_tuple
  tuple_subtract input_to_tuple element
  normalize!
end

#delete?(element) ⇒ Boolean

:call-seq:

delete?(number) -> integer or nil
delete?(star)   -> :* or nil
delete?(range)  -> sequence set or nil

Removes a specified value from the set, and returns the removed value. Returns nil if nothing was removed.

Returns an integer when the specified number argument was removed:

set = Net::IMAP::SequenceSet.new [5..10, 20]
set.delete?(7)      #=> 7
set                 #=> #<Net::IMAP::SequenceSet "5:6,8:10,20">
set.delete?("20")   #=> 20
set                 #=> #<Net::IMAP::SequenceSet "5:6,8:10">
set.delete?(30)     #=> nil

Returns :* when * or -1 is specified and removed:

set = Net::IMAP::SequenceSet.new "5:9,20,35,*"
set.delete?(-1)  #=> :*
set              #=> #<Net::IMAP::SequenceSet "5:9,20,35">

And returns a new SequenceSet when a range is specified:

set = Net::IMAP::SequenceSet.new [5..10, 20]
set.delete?(9..)  #=> #<Net::IMAP::SequenceSet "9:10,20">
set               #=> #<Net::IMAP::SequenceSet "5:8">
set.delete?(21..) #=> nil

#string will be regenerated after deletion.

Related: #delete, #delete_at, #subtract, #difference, #disjoint?

Returns:

  • (Boolean)


1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
# File 'lib/net/imap/sequence_set.rb', line 1025

def delete?(element)
  modifying! # short-circuit before input_to_tuple
  tuple = input_to_tuple element
  if tuple.first == tuple.last
    return unless include_tuple? tuple
    tuple_subtract tuple
    normalize!
    from_tuple_int tuple.first
  else
    copy = dup
    tuple_subtract tuple
    normalize!
    copy if copy.subtract(self).valid?
  end
end

#delete_at(index) ⇒ Object

:call-seq: delete_at(index) -> number or :* or nil

Deletes a number the set, indicated by the given index. Returns the number that was removed, or nil if nothing was removed.

#string will be regenerated after deletion.

Related: #delete, #delete?, #slice!, #subtract, #difference



1049
1050
1051
# File 'lib/net/imap/sequence_set.rb', line 1049

def delete_at(index)
  slice! Integer(index.to_int)
end

#disjoint?(other) ⇒ Boolean

Returns true if the set and a given object have no common elements, false otherwise.

Net::IMAP::SequenceSet["5:10"].disjoint? "7,9,11" #=> false
Net::IMAP::SequenceSet["5:10"].disjoint? "11:33"  #=> true

Related: #intersection, #intersect?

Returns:

  • (Boolean)


740
741
742
# File 'lib/net/imap/sequence_set.rb', line 740

def disjoint?(other)
  empty? || input_to_tuples(other).none? { intersect_tuple? _1 }
end

#each_elementObject

Yields each number or range (or :*) in #elements to the block and returns self. Returns an enumerator when called without a block.

The returned numbers are sorted and de-duplicated, even when the input #string is not. See #normalize, SequenceSet@Ordered+and+Normalized+sets.

Related: #elements, #each_entry



1195
1196
1197
1198
1199
# File 'lib/net/imap/sequence_set.rb', line 1195

def each_element # :yields: integer or range or :*
  return to_enum(__method__) unless block_given?
  @tuples.each do yield tuple_to_entry _1 end
  self
end

#each_entry(&block) ⇒ Object

Yields each number or range in #string to the block and returns self. Returns an enumerator when called without a block.

The entries are yielded in the same order they appear in #string, with no sorting, deduplication, or coalescing. When #string is in its normalized form, this will yield the same values as #each_element.

See SequenceSet@Ordered+and+Normalized+sets.

Related: #entries, #each_element



1183
1184
1185
1186
# File 'lib/net/imap/sequence_set.rb', line 1183

def each_entry(&block) # :yields: integer or range or :*
  return to_enum(__method__) unless block_given?
  each_entry_tuple do yield tuple_to_entry _1 end
end

#each_number(&block) ⇒ Object

Yields each number in #numbers to the block and returns self. If the set contains a *, RangeError will be raised.

Returns an enumerator when called without a block (even if the set contains *).

Related: #numbers, #each_ordered_number

Raises:

  • (RangeError)


1245
1246
1247
1248
1249
1250
# File 'lib/net/imap/sequence_set.rb', line 1245

def each_number(&block) # :yields: integer
  return to_enum(__method__) unless block_given?
  raise RangeError, '%s contains "*"' % [self.class] if include_star?
  @tuples.each do each_number_in_tuple _1, _2, &block end
  self
end

#each_ordered_number(&block) ⇒ Object

Yields each number in #entries to the block and returns self. If the set contains a *, RangeError will be raised.

Returns an enumerator when called without a block (even if the set contains *).

Related: #entries, #each_number

Raises:

  • (RangeError)


1259
1260
1261
1262
1263
# File 'lib/net/imap/sequence_set.rb', line 1259

def each_ordered_number(&block)
  return to_enum(__method__) unless block_given?
  raise RangeError, '%s contains "*"' % [self.class] if include_star?
  each_entry_tuple do each_number_in_tuple _1, _2, &block end
end

#each_rangeObject

Yields each range in #ranges to the block and returns self. Returns an enumerator when called without a block.

Related: #ranges



1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
# File 'lib/net/imap/sequence_set.rb', line 1227

def each_range # :yields: range
  return to_enum(__method__) unless block_given?
  @tuples.each do |min, max|
    if    min == STAR_INT then yield :*..
    elsif max == STAR_INT then yield min..
    else                       yield min..max
    end
  end
  self
end

#elementsObject Also known as: to_a

Returns an array of ranges and integers and :*.

The returned elements are sorted and coalesced, even when the input #string is not. * will sort last. See #normalize, SequenceSet@Ordered+and+Normalized+sets.

By itself, * translates to :*. A range containing * translates to an endless range. Use #limit to translate both cases to a maximum value.

Net::IMAP::SequenceSet["2,5:9,6,*,12:11"].elements
#=> [2, 5..9, 11..12, :*]

Related: #each_element, #ranges, #numbers



1124
# File 'lib/net/imap/sequence_set.rb', line 1124

def elements; each_element.to_a end

#empty?Boolean

Returns true if the set contains no elements

Returns:

  • (Boolean)


797
# File 'lib/net/imap/sequence_set.rb', line 797

def empty?; @tuples.empty? end

#encode_with(coder) ⇒ Object

For YAML serialization



1725
1726
1727
1728
# File 'lib/net/imap/sequence_set.rb', line 1725

def encode_with(coder) # :nodoc:
  # we can perfectly reconstruct from the string
  coder['string'] = to_s
end

#entriesObject

Returns an array of ranges and integers and :*.

The entries are in the same order they appear in #string, with no sorting, deduplication, or coalescing. When #string is in its normalized form, this will return the same result as #elements. This is useful when the given order is significant, for example in a ESEARCH response to IMAP#sort.

See SequenceSet@Ordered+and+Normalized+sets.

Related: #each_entry, #elements



1108
# File 'lib/net/imap/sequence_set.rb', line 1108

def entries; each_entry.to_a end

#eql?(other) ⇒ Boolean

:call-seq: eql?(other) -> true or false

Hash equality requires the same encoded #string representation.

Net::IMAP::SequenceSet["1:3"]  .eql? Net::IMAP::SequenceSet["1:3"]
#=> true
Net::IMAP::SequenceSet["1,2,3"].eql? Net::IMAP::SequenceSet["1:3"]
#=> false
Net::IMAP::SequenceSet["1,3"]  .eql? Net::IMAP::SequenceSet["3,1"]
#=> false
Net::IMAP::SequenceSet["9,1:*"].eql? Net::IMAP::SequenceSet["1:*"]
#=> false

Related: #==, #normalize

Returns:

  • (Boolean)


661
# File 'lib/net/imap/sequence_set.rb', line 661

def eql?(other) self.class == other.class && string == other.string end

#find_index(number) ⇒ Object

Returns the (sorted and deduplicated) index of number in the set, or nil if number isn’t in the set.

Related: #[], #at, #find_ordered_index



1339
1340
1341
1342
1343
1344
1345
1346
# File 'lib/net/imap/sequence_set.rb', line 1339

def find_index(number)
  number = to_tuple_int number
  each_tuple_with_index(@tuples) do |min, max, idx_min|
    number <  min and return nil
    number <= max and return from_tuple_int(idx_min + (number - min))
  end
  nil
end

#find_ordered_index(number) ⇒ Object

Returns the first index of number in the ordered #entries, or nil if number isn’t in the set.

Related: #find_index



1352
1353
1354
1355
1356
1357
1358
1359
1360
# File 'lib/net/imap/sequence_set.rb', line 1352

def find_ordered_index(number)
  number = to_tuple_int number
  each_tuple_with_index(each_entry_tuple) do |min, max, idx_min|
    if min <= number && number <= max
      return from_tuple_int(idx_min + (number - min))
    end
  end
  nil
end

#freezeObject

Freezes and returns the set. A frozen SequenceSet is Ractor-safe.



619
620
621
622
623
624
# File 'lib/net/imap/sequence_set.rb', line 619

def freeze
  return self if frozen?
  string
  @tuples.each(&:freeze).freeze
  super
end

#full?Boolean

Returns true if the set contains every possible element.

Returns:

  • (Boolean)


800
# File 'lib/net/imap/sequence_set.rb', line 800

def full?; @tuples == [[1, STAR_INT]] end

#has_duplicates?Boolean

:call-seq: has_duplicates? -> true | false

Returns whether or not the ordered #entries repeat any numbers.

Always returns false when #string is normalized.

Related: #entries, #count_with_duplicates, #count_duplicates?

Returns:

  • (Boolean)


1330
1331
1332
1333
# File 'lib/net/imap/sequence_set.rb', line 1330

def has_duplicates?
  return false unless @string
  count_with_duplicates != count
end

#hashObject

See #eql?



664
# File 'lib/net/imap/sequence_set.rb', line 664

def hash; [self.class, string].hash end

#include?(element) ⇒ Boolean Also known as: member?

Returns true when a given number or range is in self, and false otherwise. Returns nil when number isn’t a valid SequenceSet element (Integer, Range, *, sequence-set string).

set = Net::IMAP::SequenceSet["5:10,100,111:115"]
set.include? 1      #=> false
set.include? 5..10  #=> true
set.include? 11..20 #=> false
set.include? 100    #=> true
set.include? 6      #=> true, covered by "5:10"
set.include? 6..9   #=> true, covered by "5:10"
set.include? "6:9"  #=> true, strings are parsed
set.include? 4..9   #=> false, intersection is not sufficient
set.include? "*"    #=> false, use #limit to re-interpret "*"
set.include? -1     #=> false, -1 is interpreted as "*"

set = Net::IMAP::SequenceSet["5:10,100,111:*"]
set.include? :*     #=> true
set.include? "*"    #=> true
set.include? -1     #=> true
set.include?(200..) #=> true
set.include?(100..) #=> false

Related: #include_star?, #cover?, #===, #intersect?

Returns:

  • (Boolean)


711
712
713
714
# File 'lib/net/imap/sequence_set.rb', line 711

def include?(element)
  tuple = input_to_tuple element rescue nil
  !!include_tuple?(tuple) if tuple
end

#include_star?Boolean

Returns true when the set contains *.

Returns:

  • (Boolean)


719
# File 'lib/net/imap/sequence_set.rb', line 719

def include_star?; @tuples.last&.last == STAR_INT end

#init_with(coder) ⇒ Object

For YAML deserialization



1731
1732
1733
1734
# File 'lib/net/imap/sequence_set.rb', line 1731

def init_with(coder) # :nodoc:
  @tuples = []
  self.string = coder['string']
end

#inspectObject

Returns an inspection string for the SequenceSet.

Net::IMAP::SequenceSet.new.inspect
#=> "Net::IMAP::SequenceSet()"

Net::IMAP::SequenceSet(1..5, 1024, 15, 2000).inspect
#=> 'Net::IMAP::SequenceSet("1:5,15,1024,2000")'

Frozen sets have slightly different output:

Net::IMAP::SequenceSet.empty.inspect
#=> "Net::IMAP::SequenceSet.empty"

Net::IMAP::SequenceSet[1..5, 1024, 15, 2000].inspect
#=> 'Net::IMAP::SequenceSet["1:5,15,1024,2000"]'

Large sets (by number of #entries) have abridged output, with only the first and last entries:

Net::IMAP::SequenceSet(((1..5000) % 2).to_a).inspect
#=> #<Net::IMAP::SequenceSet 2500 entries "1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31,...(2468 entries omitted)...,4969,4971,4973,4975,4977,4979,4981,4983,4985,4987,4989,4991,4993,4995,4997,4999">

Related: #to_s, #string



1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
# File 'lib/net/imap/sequence_set.rb', line 1676

def inspect
  case (count = count_entries)
  when 0
    (frozen? ? "%s.empty" : "%s()") % [self.class]
  when ..INSPECT_MAX_LEN
    (frozen? ? "%s[%p]" : "%s(%p)") % [self.class, to_s]
  else
    if @string
      head = @string[INSPECT_ABRIDGED_HEAD_RE]
      tail = @string[INSPECT_ABRIDGED_TAIL_RE]
    else
      head = export_string_entries(@tuples.first(INSPECT_TRUNCATE_LEN)) + ","
      tail = "," + export_string_entries(@tuples.last(INSPECT_TRUNCATE_LEN))
    end
    '#<%s %d entries "%s...(%d entries omitted)...%s"%s>' % [
      self.class, count,
      head, count - INSPECT_TRUNCATE_LEN * 2, tail,
      frozen? ? " (frozen)" : "",
    ]
  end
end

#intersect?(other) ⇒ Boolean Also known as: overlap?

Returns true if the set and a given object have any common elements, false otherwise.

Net::IMAP::SequenceSet["5:10"].intersect? "7,9,11" #=> true
Net::IMAP::SequenceSet["5:10"].intersect? "11:33"  #=> false

Related: #intersection, #disjoint?, #cover?, #include?

Returns:

  • (Boolean)


728
729
730
# File 'lib/net/imap/sequence_set.rb', line 728

def intersect?(other)
  valid? && input_to_tuples(other).any? { intersect_tuple? _1 }
end

#limit(max:) ⇒ Object

Returns a frozen SequenceSet with * converted to max, numbers and ranges over max removed, and ranges containing max converted to end at max.

Net::IMAP::SequenceSet["5,10:22,50"].limit(max: 20).to_s
#=> "5,10:20"

* is always interpreted as the maximum value. When the set contains *, it will be set equal to the limit.

Net::IMAP::SequenceSet["*"].limit(max: 37)
#=> Net::IMAP::SequenceSet["37"]
Net::IMAP::SequenceSet["5:*"].limit(max: 37)
#=> Net::IMAP::SequenceSet["5:37"]
Net::IMAP::SequenceSet["500:*"].limit(max: 37)
#=> Net::IMAP::SequenceSet["37"]

Related: #limit!



1574
1575
1576
1577
1578
1579
1580
1581
# File 'lib/net/imap/sequence_set.rb', line 1574

def limit(max:)
  max = to_tuple_int(max)
  if    empty?                      then self.class.empty
  elsif !include_star? && max < min then self.class.empty
  elsif max(star: STAR_INT) <= max  then frozen? ? self : dup.freeze
  else                                   dup.limit!(max: max).freeze
  end
end

#limit!(max:) ⇒ Object

Removes all members over max and returns self. If * is a member, it will be converted to max.

Related: #limit



1587
1588
1589
1590
1591
1592
1593
1594
# File 'lib/net/imap/sequence_set.rb', line 1587

def limit!(max:)
  modifying! # short-circuit, and normalize the error message for JRuby
  star = include_star?
  max  = to_tuple_int(max)
  tuple_subtract [max + 1, STAR_INT]
  tuple_add      [max,     max     ] if star
  normalize!
end

#max(count = nil, star: :*) ⇒ Object

:call-seq:

max(star: :*) => integer or star or nil
max(count) => SequenceSet

Returns the maximum value in self, star when the set includes *, or nil when the set is empty.

When count is given, a new SequenceSet is returned, containing only the last count numbers. An empty SequenceSet is returned when self is empty. (star is ignored when count is given.)

Related: #min, #minmax, #slice



756
757
758
759
760
761
762
# File 'lib/net/imap/sequence_set.rb', line 756

def max(count = nil, star: :*)
  if count
    slice(-[count, size].min..) || remain_frozen_empty
  elsif (val = @tuples.last&.last)
    val == STAR_INT ? star : val
  end
end

#merge(*sets) ⇒ Object

Merges all of the elements that appear in any of the sets into the set, and returns self.

The sets may be any objects that would be accepted by ::new.

#string will be regenerated after all sets have been merged.

Related: #add, #add?, #union



1080
1081
1082
1083
1084
# File 'lib/net/imap/sequence_set.rb', line 1080

def merge(*sets)
  modifying! # short-circuit before input_to_tuples
  tuples_add input_to_tuples sets
  normalize!
end

#min(count = nil, star: :*) ⇒ Object

:call-seq:

min(star: :*) => integer or star or nil
min(count) => SequenceSet

Returns the minimum value in self, star when the only value in the set is *, or nil when the set is empty.

When count is given, a new SequenceSet is returned, containing only the first count numbers. An empty SequenceSet is returned when self is empty. (star is ignored when count is given.)

Related: #max, #minmax, #slice



776
777
778
779
780
781
782
# File 'lib/net/imap/sequence_set.rb', line 776

def min(count = nil, star: :*)
  if count
    slice(0...count) || remain_frozen_empty
  elsif (val = @tuples.first&.first)
    val != STAR_INT ? val : star
  end
end

#minmax(star: :*) ⇒ Object

:call-seq: minmax(star: :*) => [min, max] or nil

Returns a 2-element array containing the minimum and maximum numbers in self, or nil when the set is empty. star is handled the same way as by #min and #max.

Related: #min, #max



791
# File 'lib/net/imap/sequence_set.rb', line 791

def minmax(star: :*); [min(star: star), max(star: star)] unless empty? end

#normalizeObject

Returns a new SequenceSet with a normalized string representation.

The returned set’s #string is sorted and deduplicated. Adjacent or overlapping elements will be merged into a single larger range. See SequenceSet@Ordered+and+Normalized+sets.

Net::IMAP::SequenceSet["1:5,3:7,10:9,10:11"].normalize
#=> Net::IMAP::SequenceSet["1:7,9:11"]

Related: #normalize!, #normalized_string



1623
1624
1625
1626
1627
# File 'lib/net/imap/sequence_set.rb', line 1623

def normalize
  str = normalized_string
  return self if frozen? && str == string
  remain_frozen dup.instance_exec { @string = str&.-@; self }
end

#normalize!Object

Resets #string to be sorted, deduplicated, and coalesced. Returns self. See SequenceSet@Ordered+and+Normalized+sets.

Related: #normalize, #normalized_string



1633
1634
1635
1636
1637
# File 'lib/net/imap/sequence_set.rb', line 1633

def normalize!
  modifying! # redundant check, to normalize the error message for JRuby
  @string = nil
  self
end

#normalized_stringObject

Returns a normalized sequence-set string representation, sorted and deduplicated. Adjacent or overlapping elements will be merged into a single larger range. See SequenceSet@Ordered+and+Normalized+sets.

Net::IMAP::SequenceSet["1:5,3:7,10:9,10:11"].normalized_string
#=> "1:7,9:11"

Returns nil when the set is empty.

Related: #normalize!, #normalize, #string, #to_s



1649
1650
1651
# File 'lib/net/imap/sequence_set.rb', line 1649

def normalized_string
  @tuples.empty? ? nil : -@tuples.map { tuple_to_str _1 }.join(",")
end

#numbersObject

Returns a sorted array of all of the number values in the sequence set.

The returned numbers are sorted and de-duplicated, even when the input #string is not. See #normalize, SequenceSet@Ordered+and+Normalized+sets.

Net::IMAP::SequenceSet["2,5:9,6,12:11"].numbers
#=> [2, 5, 6, 7, 8, 9, 11, 12]

If the set contains a *, RangeError is raised. See #limit.

Net::IMAP::SequenceSet["10000:*"].numbers
#!> RangeError

WARNING: Even excluding sets with *, an enormous result can easily be created. An array with over 4 billion integers could be returned, requiring up to 32GiB of memory on a 64-bit architecture.

Net::IMAP::SequenceSet[10000..2**32-1].numbers
# ...probably freezes the process for a while...
#!> NoMemoryError (probably)

For safety, consider using #limit or #intersection to set an upper bound. Alternatively, use #each_element, #each_range, or even #each_number to avoid allocation of a result array.

Related: #elements, #ranges, #to_set



1171
# File 'lib/net/imap/sequence_set.rb', line 1171

def numbers; each_number.to_a end

#ordered_at(index) ⇒ Object

:call-seq: ordered_at(index) -> integer or nil

Returns the number at the given index in the ordered #entries, without modifying the set.

index is interpreted the same as in #at (and #[]), except that #ordered_at applies to the ordered #entries, not the sorted set.

Related: #[], #slice, #ordered_at



1407
1408
1409
# File 'lib/net/imap/sequence_set.rb', line 1407

def ordered_at(index)
  lookup_number_by_tuple_index(each_entry_tuple, index)
end

#rangesObject

Returns an array of ranges

The returned elements are sorted and coalesced, even when the input #string is not. * will sort last. See #normalize, SequenceSet@Ordered+and+Normalized+sets.

* translates to an endless range. By itself, * translates to :*... Use #limit to set * to a maximum value.

Net::IMAP::SequenceSet["2,5:9,6,*,12:11"].ranges
#=> [2..2, 5..9, 11..12, :*..]
Net::IMAP::SequenceSet["123,999:*,456:789"].ranges
#=> [123..123, 456..789, 999..]

Related: #each_range, #elements, #numbers, #to_set



1143
# File 'lib/net/imap/sequence_set.rb', line 1143

def ranges; each_range.to_a end

#replace(other) ⇒ Object

Replace the contents of the set with the contents of other and returns self.

other may be another SequenceSet or any other object that would be accepted by ::new.



548
549
550
551
552
553
554
555
556
557
558
# File 'lib/net/imap/sequence_set.rb', line 548

def replace(other)
  case other
  when SequenceSet then
    modifying! # short circuit before doing any work
    @tuples = other.deep_copy_tuples
    @string = other.instance_variable_get(:@string)
  when String      then self.string = other
  else                  clear; merge other
  end
  self
end

#send_data(imap, tag) ⇒ Object

Unstable API: for internal use only (Net::IMAP#send_data)



1720
1721
1722
# File 'lib/net/imap/sequence_set.rb', line 1720

def send_data(imap, tag) # :nodoc:
  imap.__send__(:put_string, valid_string)
end

#slice!(index, length = nil) ⇒ Object

:call-seq:

slice!(index)          -> integer or :* or nil
slice!(start, length)  -> sequence set or nil
slice!(range)          -> sequence set or nil

Deletes a number or consecutive numbers from the set, indicated by the given index, start and length, or range of offsets. Returns the number or sequence set that was removed, or nil if nothing was removed. Arguments are interpreted the same as for #slice or #[].

#string will be regenerated after deletion.

Related: #slice, #delete_at, #delete, #delete?, #subtract, #difference



1066
1067
1068
1069
1070
# File 'lib/net/imap/sequence_set.rb', line 1066

def slice!(index, length = nil)
  modifying! # short-circuit before slice
  deleted = slice(index, length) and subtract deleted
  deleted
end

#stringObject

Returns the IMAP sequence-set string representation, or nil when the set is empty. Note that an empty set is invalid in the IMAP syntax.

Use #valid_string to raise an exception when the set is empty, or #to_s to return an empty string.

If the set was created from a single string, it is not normalized. If the set is updated the string will be normalized.

Related: #valid_string, #normalized_string, #to_s, #inspect



583
# File 'lib/net/imap/sequence_set.rb', line 583

def string; @string ||= normalized_string if valid? end

#string=(input) ⇒ Object

Assigns a new string to #string and resets #elements to match. Assigning nil or an empty string are equivalent to calling #clear.

Non-empty strings are validated but not normalized.

Use #add, #merge, or #append to add a string to an existing set.

Related: #replace, #clear



597
598
599
600
601
602
603
604
605
606
607
608
609
# File 'lib/net/imap/sequence_set.rb', line 597

def string=(input)
  if input.nil?
    clear
  elsif (str = String.try_convert(input))
    modifying! # short-circuit before parsing the string
    tuples = str_to_tuples str
    @tuples, @string = [], -str
    tuples_add tuples
  else
    raise ArgumentError, "expected a string or nil, got #{input.class}"
  end
  str
end

#subtract(*sets) ⇒ Object

Removes all of the elements that appear in any of the given sets from the set, and returns self.

The sets may be any objects that would be accepted by ::new.

Related: #difference



1092
1093
1094
1095
# File 'lib/net/imap/sequence_set.rb', line 1092

def subtract(*sets)
  tuples_subtract input_to_tuples sets
  normalize!
end

#to_sObject

Returns the IMAP sequence-set string representation, or an empty string when the set is empty. Note that an empty set is invalid in the IMAP syntax.

Related: #string, #valid_string, #normalized_string, #inspect



616
# File 'lib/net/imap/sequence_set.rb', line 616

def to_s; string || "" end

#to_setObject

Returns a Set with all of the #numbers in the sequence set.

If the set contains a *, RangeError will be raised.

See #numbers for the warning about very large sets.

Related: #elements, #ranges, #numbers



1281
# File 'lib/net/imap/sequence_set.rb', line 1281

def to_set; Set.new(numbers) end

#valid?Boolean

Returns false when the set is empty.

Returns:

  • (Boolean)


794
# File 'lib/net/imap/sequence_set.rb', line 794

def valid?; !empty? end

#valid_stringObject

Returns the IMAP sequence-set string representation, or raises a DataFormatError when the set is empty.

Use #string to return nil or #to_s to return an empty string without error.

Related: #string, #normalized_string, #to_s

Raises:



567
568
569
570
# File 'lib/net/imap/sequence_set.rb', line 567

def valid_string
  raise DataFormatError, "empty sequence-set" if empty?
  string
end

#validateObject

Unstable API: currently for internal use only (Net::IMAP#validate_data)



1714
1715
1716
1717
# File 'lib/net/imap/sequence_set.rb', line 1714

def validate # :nodoc:
  empty? and raise DataFormatError, "empty sequence-set is invalid"
  self
end

#|(other) ⇒ Object Also known as: +, union

:call-seq:

self + other -> sequence set
self | other -> sequence set
union(other) -> sequence set

Returns a new sequence set that has every number in the other object added.

other may be any object that would be accepted by ::new.

Net::IMAP::SequenceSet["1:5"] | 2 | [4..6, 99]
#=> Net::IMAP::SequenceSet["1:6,99"]

Related: #add, #merge, #&, #-, #^, #~

Set identities

lhs | rhs is equivalent to:

  • rhs | lhs (commutative)

  • ~(~lhs & ~rhs) (De Morgan’s Law)

  • (lhs & rhs) ^ (lhs ^ rhs)



823
# File 'lib/net/imap/sequence_set.rb', line 823

def |(other) remain_frozen dup.merge other end

#~Object Also known as: complement

:call-seq:

~ self     -> sequence set
complement -> sequence set

Returns the complement of self, a SequenceSet which contains all numbers except for those in this set.

~Net::IMAP::SequenceSet.full  #=> Net::IMAP::SequenceSet.empty
~Net::IMAP::SequenceSet.empty #=> Net::IMAP::SequenceSet.full
~Net::IMAP::SequenceSet["1:5,100:222"]
#=> Net::IMAP::SequenceSet["6:99,223:*"]
~Net::IMAP::SequenceSet["6:99,223:*"]
#=> Net::IMAP::SequenceSet["1:5,100:222"]

Related: #complement!, #|, #&, #-, #^

Set identities

~set is equivalent to:

  • full - set, where “full” is Net::IMAP::SequenceSet.full



926
# File 'lib/net/imap/sequence_set.rb', line 926

def ~; remain_frozen dup.complement! end