123456789_123456789_123456789_123456789_123456789_

Class: Net::IMAP::SequenceSet

Relationships & Source Files
Inherits: Object
Defined in: lib/net/imap/sequence_set.rb

Overview

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

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

Creating sequence sets

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

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

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

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

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

Ordered and Normalized sets

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

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

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

Using *

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

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

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

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

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

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

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

Surprising * behavior

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

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

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

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

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

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

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

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

What’s here?

SequenceSet provides methods for:

  • Creating a SequenceSet

  • Comparing

  • Querying

  • Iterating

  • Set Operations

  • Assigning

  • Deleting

  • {Net::IMAP String Formatting}

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?: 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 in sorted set:

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

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

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

Accessing value by offset in ordered entries

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

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

Set cardinality:

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

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

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

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

Denormalized properties:

Methods for Iterating

Normalized (sorted and coalesced):

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

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

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

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

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

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

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

Order preserving:

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

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

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

Methods for Set Operations

These methods do not modify self.

  • #| (aliased as #union and #+): Returns a new set combining all members from self with all members from the other 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.

Normalized (sorted and coalesced):

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

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

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

Order preserving:

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

  • #append: Adds 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.

Methods for Deleting

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

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

  • #delete: Removes a given 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.

Constant Summary

Class Method Summary

Instance Attribute Summary

Instance Method Summary

Constructor Details

.new(input = nil) ⇒ SequenceSet

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

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

[ GitHub ]

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

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

Class Method Details

.[](*values) ⇒ valid frozen sequence set

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.

[ GitHub ]

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

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

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

[ GitHub ]

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

def empty; EMPTY end

.full

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

[ GitHub ]

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

def full;  FULL end

.try_convert(obj) ⇒ sequence set?

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.

Raises:

[ GitHub ]

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

def try_convert(obj)
  return obj if obj.is_a?(SequenceSet)
  return nil unless obj.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

Instance Attribute Details

#empty?Boolean (readonly)

Returns true if the set contains no elements

[ GitHub ]

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

def empty?; @tuples.empty? end

#full?Boolean (readonly)

Returns true if the set contains every possible element.

[ GitHub ]

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

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

#has_duplicates?Boolean (readonly)

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

Always returns false when #string is normalized.

Related: #entries, #count_with_duplicates, #count_duplicates?

[ GitHub ]

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

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

#include_star?Boolean (readonly)

Returns true when the set contains *.

[ GitHub ]

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

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

#string (rw)

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

[ GitHub ]

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

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

#string=(str) (rw)

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

[ GitHub ]

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

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

#tuples (readonly, protected)

This method is for internal use only.
[ GitHub ]

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

attr_reader :tuples # :nodoc:

#valid?Boolean (readonly)

Returns false when the set is empty.

[ GitHub ]

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

def valid?; !empty? end

Instance Method Details

#&(other) ⇒ sequence set #intersection(other) ⇒ sequence set
Also known as: #intersection

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

[ GitHub ]

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

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

#+(other)

Alias for #|.

[ GitHub ]

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

alias :+    :|

#-(other) ⇒ sequence set #difference(other) ⇒ sequence set
Also known as: #difference

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

[ GitHub ]

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

def -(other) remain_frozen dup.subtract other end

#<<(object)

Alias for #add.

[ GitHub ]

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

alias << add

#==(other) ⇒ Boolean

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

[ GitHub ]

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

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

#===(other) ⇒ Boolean

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?

[ GitHub ]

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

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

#[](index) ⇒ Integer, ... #slice(index) ⇒ Integer, ... #[](start, length) ⇒ sequence set? #slice(start, length) ⇒ sequence set? #[](range) ⇒ sequence set? #slice(range) ⇒ sequence set?
Also known as: #slice

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

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

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

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

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

If index is out of range, nil is returned.

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

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

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

Related: #at

[ GitHub ]

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

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

#^(other) ⇒ sequence set #xor(other) ⇒ sequence set
Also known as: #xor

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

[ GitHub ]

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

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

#add(object) ⇒ self #<<(other) ⇒ self
Also known as: #<<

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

[ GitHub ]

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

def add(object)
  tuple_add input_to_tuple object
  normalize!
end

#add?(object) ⇒ self?

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?

[ GitHub ]

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

def add?(object)
  add object unless include? object
end

#append(object)

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

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

[ GitHub ]

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

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

#at(index) ⇒ Integer?

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

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

Related: #[], #slice, #ordered_at

[ GitHub ]

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

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

#clear

Removes all elements and returns self.

[ GitHub ]

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

def clear; @tuples, @string = [], nil; self end

#complement

Alias for #~.

[ GitHub ]

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

alias complement :~

#complement!self

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

Related: #complement

[ GitHub ]

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

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 Also known as: #size

Returns the count of #numbers in the set.

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

Related: #count_with_duplicates

[ GitHub ]

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

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

#count_duplicates

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

When #string is normalized, this is zero.

Related: #entries, #count_with_duplicates, #has_duplicates?

[ GitHub ]

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

def count_duplicates
  return 0 unless @string
  count_with_duplicates - count
end

#count_with_duplicates

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

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

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

Related: #entries, #count_duplicates, #has_duplicates?

[ GitHub ]

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

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

#cover?(other) ⇒ Boolean

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

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

[ GitHub ]

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

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

#deconstruct

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

[ GitHub ]

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

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

#delete(object) ⇒ self

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

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

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

[ GitHub ]

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

def delete(object)
  tuple_subtract input_to_tuple object
  normalize!
end

#delete?(number) ⇒ Integer? #delete?(star) ⇒ :*? #delete?(range) ⇒ sequence set?

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?

[ GitHub ]

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

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) ⇒ Numeric, ...

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

[ GitHub ]

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

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

#difference(other)

Alias for #-.

[ GitHub ]

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

alias difference :-

#disjoint?(other) ⇒ Boolean

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

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

Related: #intersection, #intersect?

[ GitHub ]

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

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

#each_element

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

[ GitHub ]

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

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

#each_entry(&block)

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

[ GitHub ]

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

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

#each_entry_tuple(&block) (private)

[ GitHub ]

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

def each_entry_tuple(&block)
  return to_enum(__method__) unless block_given?
  if @string
    @string.split(",") do block.call str_to_tuple _1 end
  else
    @tuples.each(&block)
  end
  self
end

#each_number(&block)

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

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

Related: #numbers, #each_ordered_number

Raises:

  • (RangeError)
[ GitHub ]

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

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

#each_number_in_tuple(min, max, &block) (private)

[ GitHub ]

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

private def each_number_in_tuple(min, max, &block)
  if    min == STAR_INT then yield :*
  elsif min == max      then yield min
  elsif max != STAR_INT then (min..max).each(&block)
  else
    raise RangeError, "#{SequenceSet} cannot enumerate range with '*'"
  end
end

#each_ordered_number(&block)

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

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

Related: #entries, #each_number

Raises:

  • (RangeError)
[ GitHub ]

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

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

#each_range

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

Related: #ranges

[ GitHub ]

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

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(tuples) (private)

[ GitHub ]

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

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

#elements Also known as: #to_a

Returns an array of ranges and integers and :*.

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

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

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

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

Related: #each_element, #ranges, #numbers

[ GitHub ]

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

def elements; each_element.to_a end

#entries

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 Net::IMAP#sort.

Related: #each_entry, #elements

[ GitHub ]

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

def entries; each_entry.to_a end

#eql?(other) ⇒ Boolean

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

[ GitHub ]

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

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

#find_index(number)

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

Related: #[], #at, #find_ordered_index

[ GitHub ]

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

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

#find_ordered_index(number)

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

Related: #find_index

[ GitHub ]

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

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

#freeze

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

[ GitHub ]

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

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

#from_tuple_int(num) (private)

[ GitHub ]

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

def from_tuple_int(num) num == STAR_INT ? :* : num end

#hash

See #eql?

[ GitHub ]

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

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

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

Returns true when a given number or range is in self, and false otherwise. Returns 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?, #===

[ GitHub ]

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

def include?(element) include_tuple? input_to_tuple element end

#include_tuple?(min, max) ⇒ Boolean (private)

[ GitHub ]

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

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

#initialize_clone(other) (private)

frozen clones are shallow copied

[ GitHub ]

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

def initialize_clone(other)
  other.frozen? ? super : initialize_dup(other)
end

#initialize_dup(other) (private)

[ GitHub ]

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

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

#input_to_tuple(obj) (private)

[ GitHub ]

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

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) (private)

[ GitHub ]

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

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 Set         then obj.map      { [to_tuple_int(_1)] * 2 }
  when Array       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) (private)

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

[ GitHub ]

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

def input_try_convert(input)
  SequenceSet.try_convert(input) ||
    Integer.try_convert(input) ||
    String.try_convert(input) ||
    input
end

#inspect

[ GitHub ]

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

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) ⇒ Boolean Also known as: #overlap?

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

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

Related: #intersection, #disjoint?

[ GitHub ]

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

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

#intersect_tuple?(min, max) ⇒ Boolean (private)

[ GitHub ]

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

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

#intersection(other)

Alias for #&.

[ GitHub ]

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

alias intersection :&

#limit(max:)

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"]
[ GitHub ]

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

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

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

Related: #limit

[ GitHub ]

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

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

#lookup_number_by_tuple_index(tuples, index) (private)

[ GitHub ]

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

private def lookup_number_by_tuple_index(tuples, index)
  index = Integer(index.to_int)
  if index.negative?
    reverse_each_tuple_with_index(tuples) 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(tuples) do |min, _, idx_min, idx_max|
      index <= idx_max and return from_tuple_int(min + (index - idx_min))
    end
  end
  nil
end

#max(star: :*) ⇒ Integer, ...

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

[ GitHub ]

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

def max(star: :*)
  (val = @tuples.last&.last) && val == STAR_INT ? star : val
end

#member?(element)

Alias for #include?.

[ GitHub ]

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

alias member? include?

#merge(*inputs)

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

[ GitHub ]

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

def merge(*inputs)
  tuples_add input_to_tuples inputs
  normalize!
end

#min(star: :*) ⇒ Integer, ...

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

[ GitHub ]

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

def min(star: :*)
  (val = @tuples.first&.first) && val == STAR_INT ? star : val
end

#minmax(star: :*) ⇒ nil, ...

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

[ GitHub ]

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

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

#modifying! (private)

[ GitHub ]

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

def modifying!
  if frozen?
    raise FrozenError, "can't modify frozen #{self.class}: %p" % [self]
  end
end

#normalize

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

[ GitHub ]

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

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

#normalize!

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

Related: #normalize, #normalized_string

[ GitHub ]

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

def normalize!
  @string = nil
  self
end

#normalized_string

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

[ GitHub ]

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

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

#numbers

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

[ GitHub ]

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

def numbers; each_number.to_a end

#nz_number(num) (private)

[ GitHub ]

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

def nz_number(num)
  String === num && !/\A[1-9]\d*\z/.match?(num) and
    raise DataFormatError, "%p is not a valid nz-number" % [num]
  NumValidator.ensure_nz_number Integer num
rescue TypeError # To catch errors from Integer()
  raise DataFormatError, $!.message
end

#ordered_at(index) ⇒ Integer?

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

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

Related: #[], #slice, #ordered_at

[ GitHub ]

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

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

#overlap?(other)

Alias for #intersect?.

[ GitHub ]

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

alias overlap? intersect?

#range_gte_to(num) (private)

[ GitHub ]

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

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

#range_to_tuple(range) (private)

[ GitHub ]

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

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

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

[ GitHub ]

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

def ranges; each_range.to_a end

#remain_frozen(set) (private)

[ GitHub ]

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

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

#replace(other)

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

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

[ GitHub ]

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

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(tuples) (private)

[ GitHub ]

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

def reverse_each_tuple_with_index(tuples)
  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

#send_data(imap, tag)

This method is for internal use only.

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

[ GitHub ]

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

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

#size

Alias for #count.

[ GitHub ]

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

alias size count

#slice(index, length = nil)

Alias for #[].

[ GitHub ]

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

alias slice :[]

#slice!(index) ⇒ Integer, ... #slice!(start, length) ⇒ sequence set? #slice!(range) ⇒ sequence set?

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

[ GitHub ]

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

def slice!(index, length = nil)
  deleted = slice(index, length) and subtract deleted
  deleted
end

#slice_length(start, length) (private)

Raises:

  • (ArgumentError)
[ GitHub ]

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

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) (private)

[ GitHub ]

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

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) (private)

Raises:

[ GitHub ]

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

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) (private)

[ GitHub ]

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

def str_to_tuples(str) str.split(",", -1).map! { str_to_tuple _1 } end

#subtract(*objects)

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

[ GitHub ]

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

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

#to_a

Alias for #elements.

[ GitHub ]

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

alias to_a elements

#to_s

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

[ GitHub ]

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

def to_s; string || "" end

#to_sequence_set

Returns self

[ GitHub ]

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

alias to_sequence_set itself

#to_set

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

[ GitHub ]

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

def to_set; Set.new(numbers) end

#to_tuple_int(obj) (private)

[ GitHub ]

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

def to_tuple_int(obj) STARS.include?(obj) ? STAR_INT : nz_number(obj) end

#tuple_add(tuple) (private)

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

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

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

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

[ GitHub ]

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

def tuple_add(tuple)
  modifying!
  min, max = tuple
  lower, lower_idx = tuple_gte_with_index(min - 1)
  if    lower.nil?              then tuples << [min, max]
  elsif (max + 1) < lower.first then tuples.insert(lower_idx, [min, max])
  else  tuple_coalesce(lower, lower_idx, min, max)
  end
end

#tuple_coalesce(lower, lower_idx, min, max) (private)

[ GitHub ]

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

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) (private)

[ GitHub ]

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

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

#tuple_subtract(tuple) (private)

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

[ GitHub ]

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

def tuple_subtract(tuple)
  modifying!
  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) (private)

[ GitHub ]

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

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) (private)

[ GitHub ]

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

def tuple_to_str(tuple) tuple.uniq.map{ from_tuple_int _1 }.join(":") end

#tuple_trim_or_split(lower, idx, tmin, tmax) (private)

[ GitHub ]

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

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) (private)

[ GitHub ]

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

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

#tuples_subtract(tuples) (private)

[ GitHub ]

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

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

#tuples_trim_or_delete(lower, lower_idx, tmin, tmax) (private)

[ GitHub ]

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

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)

Alias for #|.

[ GitHub ]

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

alias union :|

#valid_string

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

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

Related: #string, #normalized_string, #to_s

Raises:

[ GitHub ]

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

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

#validate

This method is for internal use only.

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

[ GitHub ]

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

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

#xor(other)

Alias for #^.

[ GitHub ]

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

alias xor :^

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

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

[ GitHub ]

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

def |(other) remain_frozen dup.merge other end

#~(self) ⇒ sequence set #complementsequence set
Also known as: #complement

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!

[ GitHub ]

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

def ~; remain_frozen dup.complement! end