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.

EXPERIMENTAL API

SequenceSet is currently experimental. Only two methods, ::[] and valid_string, are considered stable. Although the API isn’t expected to change much, any other methods may be removed or changed without deprecation.

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, or an enumerable containing any of these.

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

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

  • ::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? (aliased as ===): 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 object (nz-number, range, or *) is contained by the set.

  • include_star?: Returns whether the set contains *.

Minimum and maximum value elements:

  • min: Returns the minimum number in the set.

  • max: Returns the maximum number in the set.

  • minmax: Returns the minimum and maximum numbers in the set.

Accessing value by offset:

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

  • at: Returns the number at a given offset.

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

Set cardinality:

  • count (aliased as size): Returns the count of numbers in the set.

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

Methods for Iterating

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

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 object.

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

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

  • #^ (aliased as xor): Returns a new set containing all members from self and the other object except those common to both.

  • #~ (aliased as complement): Returns a new set containing all members that are not in self

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

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

  • add?: If the given object is not an element in the set, adds it and returns self; otherwise, returns nil.

  • merge: Merges multiple elements into the set; returns self.

  • append: Adds a given object 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.

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

Methods for Deleting

These methods remove elements from self.

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

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

  • delete?: If the given object is an element 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 each given object from the 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.

Constants

COERCIBLE
EMPTY

intentionally defined after the class implementation

ENUMABLE
FULL
STARS

valid inputs for “*”

STAR_INT

represents “*” internally, to simplify sorting (etc)

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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 305
def [](first, *rest)
  if rest.empty?
    if first.is_a?(SequenceSet) && set.frozen? && set.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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 336
def empty; EMPTY end
full() click to toggle source

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

# File net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 339
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 348
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 326
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 623
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 605
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 441
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 471
def ===(other)
  cover?(other)
rescue
  nil
end
seqset[index] → integer or :* or nil click to toggle source
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 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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 1080
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
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 644
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 674
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 700
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 684
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 1029
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 351
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 1156
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 985
def count
  @tuples.sum(@tuples.count) { _2 - _1 } +
    (include_star? && include?(UINT32_MAX) ? -1 : 0)
end
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 483
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 712
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 749
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 772
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 535
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 924
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() { |tuple_to_entry str_to_tuple _1 end| ... } 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 tring, 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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 910
  def each_entry(&block)
    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

  # Yields each number or range (or <tt>:*</tt>) 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
  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

  private def tuple_to_entry((min, max))
    if    min == STAR_INT then :*
    elsif max == STAR_INT then min..
    elsif min == max      then min
    else                       min..max
    end
  end

  # Yields each range in #ranges to the block and returns self.
  # Returns an enumerator when called without a block.
  #
  # Related: #ranges
  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

  # Yields each number in #numbers to the block and returns self.
  # If the set contains a <tt>*</tt>, RangeError will be raised.
  #
  # Returns an enumerator when called without a block (even if the set
  # contains <tt>*</tt>).
  #
  # Related: #numbers
  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

  # Returns a Set with all of the #numbers in the sequence set.
  #
  # If the set contains a <tt>*</tt>, RangeError will be raised.
  #
  # See #numbers for the warning about very large sets.
  #
  # Related: #elements, #ranges, #numbers
  def to_set; Set.new(numbers) end

  # Returns the count of #numbers in the set.
  #
  # If <tt>*</tt> and <tt>2**32 - 1</tt> (the maximum 32-bit unsigned
  # integer value) are both in the set, they will only be counted once.
  def count
    @tuples.sum(@tuples.count) { _2 - _1 } +
      (include_star? && include?(UINT32_MAX) ? -1 : 0)
  end

  alias size count

  # Returns the index of +number+ in the set, or +nil+ if +number+ isn't in
  # the set.
  #
  # Related: #[]
  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

  private def each_tuple_with_index
    idx_min = 0
    @tuples.each do |min, max|
      yield min, max, idx_min, (idx_max = idx_min + (max - min))
      idx_min = idx_max + 1
    end
    idx_min
  end

  private def reverse_each_tuple_with_index
    idx_max = -1
    @tuples.reverse_each do |min, max|
      yield min, max, (idx_min = idx_max - (max - min)), idx_max
      idx_max = idx_min - 1
    end
    idx_max
  end

  # :call-seq: at(index) -> integer or nil
  #
  # Returns a number from +self+, without modifying the set.  Behaves the
  # same as #[], except that #at only allows a single integer argument.
  #
  # Related: #[], #slice
  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

  # :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 +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
  #
  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

  alias slice :[]

  private def slice_length(start, length)
    start  = Integer(start.to_int)
    length = Integer(length.to_int)
    raise ArgumentError, "length must be positive" unless length.positive?
    last = start + length - 1 unless start.negative? && start.abs <= length
    slice_range(start..last)
  end

  private def slice_range(range)
    first = range.begin ||  0
    last  = range.end   || -1
    last -= 1 if range.exclude_end? && range.end && last != STAR_INT
    if (first * last).positive? && last < first
      SequenceSet.empty
    elsif (min = at(first))
      max = at(last)
      if    max == :*  then self & (min..)
      elsif min <= max then self & (min..max)
      else                  SequenceSet.empty
      end
    end
  end

  # Returns a frozen SequenceSet with <tt>*</tt> 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"
  #
  # <tt>*</tt> is always interpreted as the maximum value.  When the set
  # contains <tt>*</tt>, 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"]
  #
  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

  # Removes all members over +max+ and returns self.  If <tt>*</tt> is a
  # member, it will be converted to +max+.
  #
  # Related: #limit
  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

  # :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
  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

  # 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
  def normalize
    str = normalized_string
    return self if frozen? && str == string
    remain_frozen dup.instance_exec { @string = str&.-@; self }
  end

  # Resets #string to be sorted, deduplicated, and coalesced.  Returns
  # +self+.
  #
  # Related: #normalize, #normalized_string
  def normalize!
    @string = nil
    self
  end

  # 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
  def normalized_string
    @tuples.empty? ? nil : -@tuples.map { tuple_to_str _1 }.join(",")
  end

  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

  # Returns self
  alias to_sequence_set itself

  # Unstable API: currently for internal use only (Net::IMAP#validate_data)
  def validate # :nodoc:
    empty? and raise DataFormatError, "empty sequence-set is invalid"
    self
  end

  # Unstable API: for internal use only (Net::IMAP#send_data)
  def send_data(imap, tag) # :nodoc:
    imap.__send__(:put_string, valid_string)
  end

  protected

  attr_reader :tuples # :nodoc:

  private

  def remain_frozen(set) frozen? ? set.freeze : set end

  # frozen clones are shallow copied
  def initialize_clone(other)
    other.frozen? ? super : initialize_dup(other)
  end

  def initialize_dup(other)
    @tuples = other.tuples.map(&:dup)
    @string = other.string&.-@
    super
  end

  def input_to_tuple(obj)
    obj = input_try_convert obj
    case obj
    when *STARS, Integer then [int = to_tuple_int(obj), int]
    when Range           then range_to_tuple(obj)
    when String          then str_to_tuple(obj)
    else
      raise DataFormatError, "expected number or range, got %p" % [obj]
    end
  end

  def input_to_tuples(obj)
    obj = input_try_convert obj
    case obj
    when *STARS, Integer, Range then [input_to_tuple(obj)]
    when String      then str_to_tuples obj
    when SequenceSet then obj.tuples
    when ENUMABLE    then obj.flat_map { input_to_tuples _1 }
    when nil         then []
    else
      raise DataFormatError,
            "expected nz-number, range, string, or enumerable; " \
            "got %p" % [obj]
    end
  end

  # unlike SequenceSet#try_convert, this returns an Integer, Range,
  # String, Set, Array, or... any type of object.
  def input_try_convert(input)
    SequenceSet.try_convert(input) ||
      # Integer.try_convert(input) || # ruby 3.1+
      input.respond_to?(:to_int) && Integer(input.to_int) ||
      String.try_convert(input) ||
      input
  end

  def range_to_tuple(range)
    first = to_tuple_int(range.begin || 1)
    last  = to_tuple_int(range.end   || :*)
    last -= 1 if range.exclude_end? && range.end && last != STAR_INT
    unless first <= last
      raise DataFormatError, "invalid range for sequence-set: %p" % [range]
    end
    [first, last]
  end

  def to_tuple_int(obj) STARS.include?(obj) ? STAR_INT : nz_number(obj) end
  def from_tuple_int(num) num == STAR_INT ? :* : num end

  def tuple_to_str(tuple) tuple.uniq.map{ from_tuple_int _1 }.join(":") end
  def str_to_tuples(str) str.split(",", -1).map! { str_to_tuple _1 } end
  def str_to_tuple(str)
    raise DataFormatError, "invalid sequence set string" if str.empty?
    str.split(":", 2).map! { to_tuple_int _1 }.minmax
  end

  def include_tuple?((min, max)) range_gte_to(min)&.cover?(min..max) end

  def intersect_tuple?((min, max))
    range = range_gte_to(min) and
      range.include?(min) || range.include?(max) || (min..max).cover?(range)
  end

  def tuples_add(tuples)      tuples.each do tuple_add _1      end; self end
  def tuples_subtract(tuples) tuples.each do tuple_subtract _1 end; self end

  #
  #   --|=====| |=====new tuple=====|                 append
  #   ?????????-|=====new tuple=====|-|===lower===|-- insert
  #
  #             |=====new tuple=====|
  #   ---------??=======lower=======??--------------- noop
  #
  #   ---------??===lower==|--|==|                    join remaining
  #   ---------??===lower==|--|==|----|===upper===|-- join until upper
  #   ---------??===lower==|--|==|--|=====upper===|-- join to upper
  def tuple_add(tuple)
    min, max = tuple
    lower, lower_idx = tuple_gte_with_index(min - 1)
    if    lower.nil?              then tuples << tuple
    elsif (max + 1) < lower.first then tuples.insert(lower_idx, tuple)
    else  tuple_coalesce(lower, lower_idx, min, max)
    end
  end

  def tuple_coalesce(lower, lower_idx, min, max)
    return if lower.first <= min && max <= lower.last
    lower[0] = [min, lower.first].min
    lower[1] = [max, lower.last].max
    lower_idx += 1
    return if lower_idx == tuples.count
    tmax_adj = lower.last + 1
    upper, upper_idx = tuple_gte_with_index(tmax_adj)
    if upper
      tmax_adj < upper.first ? (upper_idx -= 1) : (lower[1] = upper.last)
    end
    tuples.slice!(lower_idx..upper_idx)
  end

  #         |====tuple================|
  # --|====|                               no more       1. noop
  # --|====|---------------------------|====lower====|-- 2. noop
  # -------|======lower================|---------------- 3. split
  # --------|=====lower================|---------------- 4. trim beginning
  #
  # -------|======lower====????????????----------------- trim lower
  # --------|=====lower====????????????----------------- delete lower
  #
  # -------??=====lower===============|----------------- 5. trim/delete one
  # -------??=====lower====|--|====|       no more       6. delete rest
  # -------??=====lower====|--|====|---|====upper====|-- 7. delete until
  # -------??=====lower====|--|====|--|=====upper====|-- 8. delete and trim
  def tuple_subtract(tuple)
    min, max = tuple
    lower, idx = tuple_gte_with_index(min)
    if    lower.nil?        then nil # case 1.
    elsif max < lower.first then nil # case 2.
    elsif max < lower.last  then tuple_trim_or_split   lower, idx, min, max
    else                         tuples_trim_or_delete lower, idx, min, max
    end
  end

  def tuple_trim_or_split(lower, idx, tmin, tmax)
    if lower.first < tmin # split
      tuples.insert(idx, [lower.first, tmin - 1])
    end
    lower[0] = tmax + 1
  end

  def tuples_trim_or_delete(lower, lower_idx, tmin, tmax)
    if lower.first < tmin # trim lower
      lower[1] = tmin - 1
      lower_idx += 1
    end
    if tmax == lower.last                           # case 5
      upper_idx = lower_idx
    elsif (upper, upper_idx = tuple_gte_with_index(tmax + 1))
      upper_idx -= 1                                # cases 7 and 8
      upper[0] = tmax + 1 if upper.first <= tmax    # case 8 (else case 7)
    end
    tuples.slice!(lower_idx..upper_idx)
  end

  def tuple_gte_with_index(num)
    idx = tuples.bsearch_index { _2 >= num } and [tuples[idx], idx]
  end

  def range_gte_to(num)
    first, last = tuples.bsearch { _2 >= num }
    first..last if first
  end

  def nz_number(num)
    case num
    when Integer, /\A[1-9]\d*\z/ then num = Integer(num)
    else raise DataFormatError, "%p is not a valid nz-number" % [num]
    end
    NumValidator.ensure_nz_number(num)
    num
  end

  # intentionally defined after the class implementation

  EMPTY = new.freeze
  FULL  = self["1:*"]
  private_constant :EMPTY, :FULL

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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 960
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 942
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
each_tuple_with_index() { |min, max, idx_min, (idx_max = idx_min + (max - min))| ... } click to toggle source
# File net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 1005
        def each_tuple_with_index
  idx_min = 0
  @tuples.each do |min, max|
    yield min, max, idx_min, (idx_max = idx_min + (max - min))
    idx_min = idx_max + 1
  end
  idx_min
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 851
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 565
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 833
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 460
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 996
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 418
def freeze
  return self if frozen?
  string
  @tuples.each(&:freeze).freeze
  super
end
from_tuple_int(num) click to toggle source
# File net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 1292
def from_tuple_int(num) num == STAR_INT ? :* : num end
full?() click to toggle source

Returns true if the set contains every possible element.

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

See eql?

# File net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 463
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 509
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 514
def include_star?; @tuples.last&.last == STAR_INT end
include_tuple?((min, max)) click to toggle source
# File net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 1301
def include_tuple?((min, max)) range_gte_to(min)&.cover?(min..max) end
initialize_clone(other) click to toggle source

frozen clones are shallow copied

Calls superclass method
# File net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 1235
def initialize_clone(other)
  other.frozen? ? super : initialize_dup(other)
end
initialize_dup(other) click to toggle source
Calls superclass method
# File net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 1239
def initialize_dup(other)
  @tuples = other.tuples.map(&:dup)
  @string = other.string&.-@
  super
end
input_to_tuple(obj) click to toggle source
# File net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 1245
def input_to_tuple(obj)
  obj = input_try_convert obj
  case obj
  when *STARS, Integer then [int = to_tuple_int(obj), int]
  when Range           then range_to_tuple(obj)
  when String          then str_to_tuple(obj)
  else
    raise DataFormatError, "expected number or range, got %p" % [obj]
  end
end
input_to_tuples(obj) click to toggle source
# File net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 1256
def input_to_tuples(obj)
  obj = input_try_convert obj
  case obj
  when *STARS, Integer, Range then [input_to_tuple(obj)]
  when String      then str_to_tuples obj
  when SequenceSet then obj.tuples
  when ENUMABLE    then obj.flat_map { input_to_tuples _1 }
  when nil         then []
  else
    raise DataFormatError,
          "expected nz-number, range, string, or enumerable; " \
          "got %p" % [obj]
  end
end
input_try_convert(input) click to toggle source

unlike SequenceSet#try_convert, this returns an Integer, Range, String, Set, Array, or… any type of object.

# File net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 1273
def input_try_convert(input)
  SequenceSet.try_convert(input) ||
    # Integer.try_convert(input) || # ruby 3.1+
    input.respond_to?(:to_int) && Integer(input.to_int) ||
    String.try_convert(input) ||
    input
end
inspect() click to toggle source
# File net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 1202
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 523
def intersect?(other)
  valid? && input_to_tuples(other).any? { intersect_tuple? _1 }
end
Also aliased as: overlap?
intersect_tuple?((min, max)) click to toggle source
# File net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 1303
def intersect_tuple?((min, max))
  range = range_gte_to(min) and
    range.include?(min) || range.include?(max) || (min..max).cover?(range)
end
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 1129
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 1142
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 543
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 805
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 551
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 559
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 1175
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 1185
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 1198
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 900
def numbers; each_number.to_a end
nz_number(num) click to toggle source
# File net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 1397
def nz_number(num)
  case num
  when Integer, /\A[1-9]\d*\z/ then num = Integer(num)
  else raise DataFormatError, "%p is not a valid nz-number" % [num]
  end
  NumValidator.ensure_nz_number(num)
  num
end
overlap?(other)
Alias for: intersect?
range_gte_to(num) click to toggle source
# File net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 1392
def range_gte_to(num)
  first, last = tuples.bsearch { _2 >= num }
  first..last if first
end
range_to_tuple(range) click to toggle source
# File net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 1281
def range_to_tuple(range)
  first = to_tuple_int(range.begin || 1)
  last  = to_tuple_int(range.end   || :*)
  last -= 1 if range.exclude_end? && range.end && last != STAR_INT
  unless first <= last
    raise DataFormatError, "invalid range for sequence-set: %p" % [range]
  end
  [first, last]
end
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 872
def ranges; each_range.to_a end
remain_frozen(set) click to toggle source
# File net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 1232
def remain_frozen(set) frozen? ? set.freeze : set 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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 358
def replace(other)
  case other
  when SequenceSet then initialize_dup(other)
  when String      then self.string = other
  else                  clear; merge other
  end
  self
end
reverse_each_tuple_with_index() { |min, max, (idx_min = idx_max - (max - min)), idx_max| ... } click to toggle source
# File net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 1014
        def reverse_each_tuple_with_index
  idx_max = -1
  @tuples.reverse_each do |min, max|
    yield min, max, (idx_min = idx_max - (max - min)), idx_max
    idx_max = idx_min - 1
  end
  idx_max
end
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 789
def slice!(index, length = nil)
  deleted = slice(index, length) and subtract deleted
  deleted
end
slice_length(start, length) click to toggle source
# File net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 1089
        def slice_length(start, length)
  start  = Integer(start.to_int)
  length = Integer(length.to_int)
  raise ArgumentError, "length must be positive" unless length.positive?
  last = start + length - 1 unless start.negative? && start.abs <= length
  slice_range(start..last)
end
slice_range(range) click to toggle source
# File net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 1097
        def slice_range(range)
  first = range.begin ||  0
  last  = range.end   || -1
  last -= 1 if range.exclude_end? && range.end && last != STAR_INT
  if (first * last).positive? && last < first
    SequenceSet.empty
  elsif (min = at(first))
    max = at(last)
    if    max == :*  then self & (min..)
    elsif min <= max then self & (min..max)
    else                  SequenceSet.empty
    end
  end
end
str_to_tuple(str) click to toggle source
# File net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 1296
def str_to_tuple(str)
  raise DataFormatError, "invalid sequence set string" if str.empty?
  str.split(":", 2).map! { to_tuple_int _1 }.minmax
end
str_to_tuples(str) click to toggle source
# File net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 1295
def str_to_tuples(str) str.split(",", -1).map! { str_to_tuple _1 } 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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 390
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 399
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 819
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 415
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 979
def to_set; Set.new(numbers) end
to_tuple_int(obj) click to toggle source
# File net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 1291
def to_tuple_int(obj) STARS.include?(obj) ? STAR_INT : nz_number(obj) end
tuple_add(tuple) click to toggle source

–|=====| |=====new tuple=====| append ?????????-|=====new tuple=====|-|===lower===|– insert

|=====new tuple=====|

———??=======lower=======??————— noop

———??===lower==|–|==| join remaining ———??===lower==|–|==|—-|===upper===|– join until upper ———??===lower==|–|==|–|=====upper===|– join to upper

# File net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 1321
def tuple_add(tuple)
  min, max = tuple
  lower, lower_idx = tuple_gte_with_index(min - 1)
  if    lower.nil?              then tuples << tuple
  elsif (max + 1) < lower.first then tuples.insert(lower_idx, tuple)
  else  tuple_coalesce(lower, lower_idx, min, max)
  end
end
tuple_coalesce(lower, lower_idx, min, max) click to toggle source
# File net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 1330
def tuple_coalesce(lower, lower_idx, min, max)
  return if lower.first <= min && max <= lower.last
  lower[0] = [min, lower.first].min
  lower[1] = [max, lower.last].max
  lower_idx += 1
  return if lower_idx == tuples.count
  tmax_adj = lower.last + 1
  upper, upper_idx = tuple_gte_with_index(tmax_adj)
  if upper
    tmax_adj < upper.first ? (upper_idx -= 1) : (lower[1] = upper.last)
  end
  tuples.slice!(lower_idx..upper_idx)
end
tuple_gte_with_index(num) click to toggle source
# File net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 1388
def tuple_gte_with_index(num)
  idx = tuples.bsearch_index { _2 >= num } and [tuples[idx], idx]
end
tuple_subtract(tuple) click to toggle source
|====tuple================|

–|====| no more 1. noop –|====|—————————|====lower====|– 2. noop ——-|======lower================|—————- 3. split ——–|=====lower================|—————- 4. trim beginning

——-|======lower====????????????—————– trim lower ——–|=====lower====????????????—————– delete lower

——-??=====lower===============|—————– 5. trim/delete one ——-??=====lower====|–|====| no more 6. delete rest ——-??=====lower====|–|====|—|====upper====|– 7. delete until ——-??=====lower====|–|====|–|=====upper====|– 8. delete and trim

# File net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 1357
def tuple_subtract(tuple)
  min, max = tuple
  lower, idx = tuple_gte_with_index(min)
  if    lower.nil?        then nil # case 1.
  elsif max < lower.first then nil # case 2.
  elsif max < lower.last  then tuple_trim_or_split   lower, idx, min, max
  else                         tuples_trim_or_delete lower, idx, min, max
  end
end
tuple_to_entry((min, max)) click to toggle source
# File net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 930
        def tuple_to_entry((min, max))
  if    min == STAR_INT then :*
  elsif max == STAR_INT then min..
  elsif min == max      then min
  else                       min..max
  end
end
tuple_to_str(tuple) click to toggle source
# File net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 1294
def tuple_to_str(tuple) tuple.uniq.map{ from_tuple_int _1 }.join(":") end
tuple_trim_or_split(lower, idx, tmin, tmax) click to toggle source
# File net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 1367
def tuple_trim_or_split(lower, idx, tmin, tmax)
  if lower.first < tmin # split
    tuples.insert(idx, [lower.first, tmin - 1])
  end
  lower[0] = tmax + 1
end
tuples_add(tuples) click to toggle source
# File net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 1308
def tuples_add(tuples)      tuples.each do tuple_add _1      end; self end
tuples_subtract(tuples) click to toggle source
# File net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 1309
def tuples_subtract(tuples) tuples.each do tuple_subtract _1 end; self end
tuples_trim_or_delete(lower, lower_idx, tmin, tmax) click to toggle source
# File net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 1374
def tuples_trim_or_delete(lower, lower_idx, tmin, tmax)
  if lower.first < tmin # trim lower
    lower[1] = tmin - 1
    lower_idx += 1
  end
  if tmax == lower.last                           # case 5
    upper_idx = lower_idx
  elsif (upper, upper_idx = tuple_gte_with_index(tmax + 1))
    upper_idx -= 1                                # cases 7 and 8
    upper[0] = tmax + 1 if upper.first <= tmax    # case 8 (else case 7)
  end
  tuples.slice!(lower_idx..upper_idx)
end
union(other) → sequence set
Alias for: |
valid?() click to toggle source

Returns false when the set is empty.

# File net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 562
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 374
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 586
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 net-imap-0.4.9/lib/net/imap/sequence_set.rb, line 662
def ~; remain_frozen dup.complement! end
Also aliased as: complement