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 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"

Normalized form

When a sequence set is created with a single String value, that string representation is preserved. SequenceSet’s internal representation implicitly sorts all entries, de-duplicates numbers, and coalesces adjacent or overlapping ranges. Most enumeration methods and offset-based methods use this normalized representation. Most modification methods will convert string to its normalized form.

In some cases the order of the string representation is significant, such as the ESORT, CONTEXT=SORT, and UIDPLUS extensions. Use entries or each_entry to enumerate the set in its original order. 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 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

Constants

UINT32_MAX

The largest possible non-zero unsigned 32-bit integer

Public Class Methods

SequenceSet[*values] → valid frozen sequence set click to toggle source

Returns a frozen SequenceSet, constructed from values.

An empty SequenceSet is invalid and will raise a DataFormatError.

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

# File lib/net/imap/sequence_set.rb, line 298
def [](first, *rest)
  if rest.empty?
    if first.is_a?(SequenceSet) && first.frozen? && first.valid?
      first
    else
      new(first).validate.freeze
    end
  else
    new(first).merge(*rest).validate.freeze
  end
end
empty click to toggle source

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

# File lib/net/imap/sequence_set.rb, line 329
def empty; EMPTY end
full click to toggle source

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

# File lib/net/imap/sequence_set.rb, line 332
def full;  FULL end
new(input = nil) click to toggle source

Create a new SequenceSet object from input, which may be another SequenceSet, an IMAP formatted sequence-set string, a number, a range, :*, or an enumerable of these.

Use ::[] to create a frozen (non-empty) SequenceSet.

# File lib/net/imap/sequence_set.rb, line 341
def initialize(input = nil) input ? replace(input) : clear end
try_convert(obj) → sequence set or nil click to toggle source

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, an exception is raised.

# File lib/net/imap/sequence_set.rb, line 319
def try_convert(obj)
  return obj if obj.is_a?(SequenceSet)
  return nil unless respond_to?(:to_sequence_set)
  obj = obj.to_sequence_set
  return obj if obj.is_a?(SequenceSet)
  raise DataFormatError, "invalid object returned from to_sequence_set"
end

Public Instance Methods

self & other → sequence set click to toggle source

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: a non-zero 32 bit unsigned integer, range, sequence-set formatted string, another sequence set, or an enumerable containing any of these.

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

(seqset & other) is equivalent to (seqset - ~other).

# File lib/net/imap/sequence_set.rb, line 616
def &(other)
  remain_frozen dup.subtract SequenceSet.new(other).complement!
end
Also aliased as: intersection
self + other → sequence set
Alias for: |
self - other → sequence set click to toggle source

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: a non-zero 32 bit unsigned integer, range, sequence-set formatted string, another sequence set, or an enumerable containing any of these.

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

Related: subtract

# File lib/net/imap/sequence_set.rb, line 598
def -(other) remain_frozen dup.subtract other end
Also aliased as: difference
self << other → self
Alias for: add
self == other → true or false click to toggle source

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

# File lib/net/imap/sequence_set.rb, line 434
def ==(other)
  self.class == other.class &&
    (to_s == other.to_s || tuples == other.tuples)
end
self === other → true | false | nil click to toggle source

Returns whether other is contained within the set. Returns nil if a StandardError is raised while converting other to a comparable type.

Related: cover?, include?, include_star?

# File lib/net/imap/sequence_set.rb, line 464
def ===(other)
  cover?(other)
rescue
  nil
end
seqset[index] → integer or :* or nil click to toggle source
seqset[start, length] → sequence set or nil
seqset[range] → sequence set or nil

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

When an Integer argument index is given, the number at offset index 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 self:

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 normalized set—sorted and de-duplicated—not on the assigned value of string.

set = Net::IMAP::SequenceSet["12,20:23,11:16,21"]
set[0]   #=> 11
set[-1]  #=> 23
# File lib/net/imap/sequence_set.rb, line 1081
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
Also aliased as: slice
self ^ other → sequence set click to toggle source

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: a non-zero 32 bit unsigned integer, range, sequence-set formatted string, another sequence set, or an enumerable containing any of these.

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

(seqset ^ other) is equivalent to ((seqset | other) - (seqset & other)).

# File lib/net/imap/sequence_set.rb, line 637
def ^(other) remain_frozen (self | other).subtract(self & other) end
Also aliased as: xor
add(object) → self click to toggle source

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

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

Related: add?, merge, union

# File lib/net/imap/sequence_set.rb, line 667
def add(object)
  tuple_add input_to_tuple object
  normalize!
end
Also aliased as: <<
add?(object) → self or nil click to toggle source

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

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

Related: add, merge, union, include?

# File lib/net/imap/sequence_set.rb, line 693
def add?(object)
  add object unless include? object
end
append(object) click to toggle source

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.

# File lib/net/imap/sequence_set.rb, line 677
def append(object)
  tuple = input_to_tuple object
  entry = tuple_to_str tuple
  tuple_add tuple
  @string = -(string ? "#{@string},#{entry}" : entry)
  self
end
at(index) → integer or nil click to toggle source

Returns a number from self, without modifying the set. Behaves the same as [], except that at only allows a single integer argument.

Related: [], slice

# File lib/net/imap/sequence_set.rb, line 1030
def at(index)
  index = Integer(index.to_int)
  if index.negative?
    reverse_each_tuple_with_index do |min, max, idx_min, idx_max|
      idx_min <= index and return from_tuple_int(min + (index - idx_min))
    end
  else
    each_tuple_with_index do |min, _, idx_min, idx_max|
      index <= idx_max and return from_tuple_int(min + (index - idx_min))
    end
  end
  nil
end
clear click to toggle source

Removes all elements and returns self.

# File lib/net/imap/sequence_set.rb, line 344
def clear; @tuples, @string = [], nil; self end
complement → sequence set
Alias for: ~
complement! → self click to toggle source

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

Related: complement

# File lib/net/imap/sequence_set.rb, line 1161
def complement!
  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
count click to toggle source

Returns the count of numbers in the set.

If * and 2**32 - 1 (the maximum 32-bit unsigned integer value) are both in the set, they will only be counted once.

# File lib/net/imap/sequence_set.rb, line 982
def count
  @tuples.sum(@tuples.count) { _2 - _1 } +
    (include_star? && include?(UINT32_MAX) ? -1 : 0)
end
Also aliased as: size
cover?(other) → true | false | nil click to toggle source

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

Related: ===, include?, include_star?

# File lib/net/imap/sequence_set.rb, line 476
def cover?(other) input_to_tuples(other).none? { !include_tuple?(_1) } end
delete(object) → self click to toggle source

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

# File lib/net/imap/sequence_set.rb, line 705
def delete(object)
  tuple_subtract input_to_tuple object
  normalize!
end
delete?(number) → integer or nil click to toggle source
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?

# File lib/net/imap/sequence_set.rb, line 742
def delete?(object)
  tuple = input_to_tuple object
  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) → number or :* or nil click to toggle source

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

# File lib/net/imap/sequence_set.rb, line 765
def delete_at(index)
  slice! Integer(index.to_int)
end
difference(other) → sequence set
Alias for: -
disjoint?(other) click to toggle source

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?

# File lib/net/imap/sequence_set.rb, line 528
def disjoint?(other)
  empty? || input_to_tuples(other).none? { intersect_tuple? _1 }
end
each_element{ |integer or range or :*| ... } click to toggle source

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.

Related: elements, each_entry

# File lib/net/imap/sequence_set.rb, line 917
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{ |integer or range or :*| ... } click to toggle source

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.

Related: entries, each_element

# File lib/net/imap/sequence_set.rb, line 903
def each_entry(&block) # :yields: integer or range or :*
  return to_enum(__method__) unless block_given?
  return each_element(&block) unless @string
  @string.split(",").each do yield tuple_to_entry str_to_tuple _1 end
  self
end
each_number{ |integer| ... } click to toggle source

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

# File lib/net/imap/sequence_set.rb, line 957
def each_number(&block) # :yields: integer
  return to_enum(__method__) unless block_given?
  raise RangeError, '%s contains "*"' % [self.class] if include_star?
  each_element do |elem|
    case elem
    when Range   then elem.each(&block)
    when Integer then block.(elem)
    end
  end
  self
end
each_range{ |range| ... } click to toggle source

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

Related: ranges

# File lib/net/imap/sequence_set.rb, line 939
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
elements click to toggle source

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.

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

If the original input was unordered or contains overlapping ranges, the returned ranges will be ordered and coalesced.

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

Related: each_element, ranges, numbers

# File lib/net/imap/sequence_set.rb, line 844
def elements; each_element.to_a end
Also aliased as: to_a
empty? click to toggle source

Returns true if the set contains no elements

# File lib/net/imap/sequence_set.rb, line 558
def empty?; @tuples.empty? end
entries click to toggle source

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.

Related: each_entry, elements

# File lib/net/imap/sequence_set.rb, line 826
def entries; each_entry.to_a end
eql?(other) → true or false click to toggle source

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

# File lib/net/imap/sequence_set.rb, line 453
def eql?(other) self.class == other.class && string == other.string end
find_index(number) click to toggle source

Returns the index of number in the set, or nil if number isn’t in the set.

Related: []

# File lib/net/imap/sequence_set.rb, line 993
def find_index(number)
  number = to_tuple_int number
  each_tuple_with_index do |min, max, idx_min|
    number <  min and return nil
    number <= max and return from_tuple_int(idx_min + (number - min))
  end
  nil
end
freeze click to toggle source

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

Calls superclass method
# File lib/net/imap/sequence_set.rb, line 411
def freeze
  return self if frozen?
  string
  @tuples.each(&:freeze).freeze
  super
end
full? click to toggle source

Returns true if the set contains every possible element.

# File lib/net/imap/sequence_set.rb, line 561
def full?; @tuples == [[1, STAR_INT]] end
hash click to toggle source

See eql?

# File lib/net/imap/sequence_set.rb, line 456
def hash; [self.class, string].hash end
include?(element) click to toggle source

Returns true when a given number or range is in self, and false otherwise. Returns false unless number is an Integer, Range, or *.

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? 4..9   #=> true, covered by "5:10"
set.include? "4: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?, ===

# File lib/net/imap/sequence_set.rb, line 502
def include?(element) include_tuple? input_to_tuple element end
Also aliased as: member?
include_star? click to toggle source

Returns true when the set contains *.

# File lib/net/imap/sequence_set.rb, line 507
def include_star?; @tuples.last&.last == STAR_INT end
inspect click to toggle source
# File lib/net/imap/sequence_set.rb, line 1207
def inspect
  if empty?
    (frozen? ?  "%s.empty" : "#<%s empty>") % [self.class]
  elsif frozen?
    "%s[%p]"   % [self.class, to_s]
  else
    "#<%s %p>" % [self.class, to_s]
  end
end
intersect?(other) click to toggle source

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?

# File lib/net/imap/sequence_set.rb, line 516
def intersect?(other)
  valid? && input_to_tuples(other).any? { intersect_tuple? _1 }
end
Also aliased as: overlap?
intersection(other) → sequence set
Alias for: &
limit(max:) click to toggle source

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"]
# File lib/net/imap/sequence_set.rb, line 1134
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:) click to toggle source

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

Related: limit

# File lib/net/imap/sequence_set.rb, line 1147
def limit!(max:)
  star = include_star?
  max  = to_tuple_int(max)
  tuple_subtract [max + 1, STAR_INT]
  tuple_add      [max,     max     ] if star
  normalize!
end
max(star: :*) → integer or star or nil click to toggle source

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

# File lib/net/imap/sequence_set.rb, line 536
def max(star: :*)
  (val = @tuples.last&.last) && val == STAR_INT ? star : val
end
member?(element)
Alias for: include?
merge(*inputs) click to toggle source

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

The inputs may be any objects that would be accepted by ::new: non-zero 32 bit unsigned integers, ranges, sequence-set formatted strings, other sequence sets, or enumerables containing any of these.

string will be regenerated after all inputs have been merged.

Related: add, add?, union

# File lib/net/imap/sequence_set.rb, line 798
def merge(*inputs)
  tuples_add input_to_tuples inputs
  normalize!
end
min(star: :*) → integer or star or nil click to toggle source

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

# File lib/net/imap/sequence_set.rb, line 544
def min(star: :*)
  (val = @tuples.first&.first) && val == STAR_INT ? star : val
end
minmax(star: :*) → nil or [integer, integer or star] click to toggle source

Returns a 2-element array containing the minimum and maximum numbers in self, or nil when the set is empty.

# File lib/net/imap/sequence_set.rb, line 552
def minmax(star: :*); [min(star: star), max(star: star)] unless empty? end
normalize click to toggle source

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.

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

Related: normalize!, normalized_string

# File lib/net/imap/sequence_set.rb, line 1180
def normalize
  str = normalized_string
  return self if frozen? && str == string
  remain_frozen dup.instance_exec { @string = str&.-@; self }
end
normalize! click to toggle source

Resets string to be sorted, deduplicated, and coalesced. Returns self.

Related: normalize, normalized_string

# File lib/net/imap/sequence_set.rb, line 1190
def normalize!
  @string = nil
  self
end
normalized_string click to toggle source

Returns a normalized sequence-set string representation, sorted and deduplicated. Adjacent or overlapping elements will be merged into a single larger range. Returns nil when the set is empty.

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

Related: normalize!, normalize

# File lib/net/imap/sequence_set.rb, line 1203
def normalized_string
  @tuples.empty? ? nil : -@tuples.map { tuple_to_str _1 }.join(",")
end
numbers click to toggle source

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.

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

# File lib/net/imap/sequence_set.rb, line 893
def numbers; each_number.to_a end
overlap?(other)
Alias for: intersect?
ranges click to toggle source

Returns an array of ranges

The returned elements are sorted and coalesced, even when the input string is not. * will sort last. See normalize.

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

The returned ranges will be ordered and coalesced, even when the input string is not. * will sort last. See normalize.

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

# File lib/net/imap/sequence_set.rb, line 865
def ranges; each_range.to_a end
replace(other) click to toggle source

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

other may be another SequenceSet, or it may be an IMAP sequence-set string, a number, a range, *, or an enumerable of these.

# File lib/net/imap/sequence_set.rb, line 351
def replace(other)
  case other
  when SequenceSet then initialize_dup(other)
  when String      then self.string = other
  else                  clear; merge other
  end
  self
end
size
Alias for: count
slice(index) → integer or :* or nil
slice(start, length) → sequence set or nil
slice(range) → sequence set or nil
Alias for: []
slice!(index) → integer or :* or nil click to toggle source
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

# File lib/net/imap/sequence_set.rb, line 782
def slice!(index, length = nil)
  deleted = slice(index, length) and subtract deleted
  deleted
end
string click to toggle source

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

# File lib/net/imap/sequence_set.rb, line 383
def string; @string ||= normalized_string if valid? end
string=(str) click to toggle source

Assigns a new string to string and resets elements to match. It cannot be set to an empty string—assign nil or use clear instead. The string is validated but not normalized.

Use add or merge to add a string to an existing set.

Related: replace, clear

# File lib/net/imap/sequence_set.rb, line 392
def string=(str)
  if str.nil?
    clear
  else
    str = String.try_convert(str) or raise ArgumentError, "not a string"
    tuples = str_to_tuples str
    @tuples, @string = [], -str
    tuples_add tuples
  end
end
subtract(*objects) click to toggle source

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

The objects may be any objects that would be accepted by ::new: non-zero 32 bit unsigned integers, ranges, sequence-set formatted strings, other sequence sets, or enumerables containing any of these.

Related: difference

# File lib/net/imap/sequence_set.rb, line 812
def subtract(*objects)
  tuples_subtract input_to_tuples objects
  normalize!
end
to_a
Alias for: elements
to_s click to toggle source

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: valid_string, normalized_string, to_s

# File lib/net/imap/sequence_set.rb, line 408
def to_s; string || "" end
to_set click to toggle source

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

# File lib/net/imap/sequence_set.rb, line 976
def to_set; Set.new(numbers) end
union(other) → sequence set
Alias for: |
valid? click to toggle source

Returns false when the set is empty.

# File lib/net/imap/sequence_set.rb, line 555
def valid?; !empty? end
valid_string click to toggle source

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

# File lib/net/imap/sequence_set.rb, line 367
def valid_string
  raise DataFormatError, "empty sequence-set" if empty?
  string
end
xor(other) → sequence set
Alias for: ^
self | other → sequence set click to toggle source

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: a non-zero 32 bit unsigned integer, range, sequence-set formatted string, another sequence set, or an enumerable containing any of these.

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

Related: add, merge

# File lib/net/imap/sequence_set.rb, line 579
def |(other) remain_frozen dup.merge other end
Also aliased as: +, union
~ self → sequence set click to toggle source

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!

# File lib/net/imap/sequence_set.rb, line 655
def ~; remain_frozen dup.complement! end
Also aliased as: complement