Class: Net::IMAP::SequenceSet

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

Overview

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

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

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.

Constant Summary collapse

UINT32_MAX =

The largest possible non-zero unsigned 32-bit integer

2**32 - 1

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(input = nil) ⇒ SequenceSet

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

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



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

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

Class Method Details

.[](first, *rest) ⇒ Object

:call-seq:

SequenceSet[*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.



305
306
307
308
309
310
311
312
313
314
315
# File '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

.emptyObject

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



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

def empty; EMPTY end

.fullObject

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



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

def full;  FULL end

.try_convert(obj) ⇒ Object

:call-seq:

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

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

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

Raises:



326
327
328
329
330
331
332
# File '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

Instance Method Details

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

:call-seq:

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

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

other may be any object that would be accepted by ::new: 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).



623
624
625
# File 'lib/net/imap/sequence_set.rb', line 623

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

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

:call-seq:

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

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

other may be any object that would be accepted by ::new: 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



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

def -(other) remain_frozen dup.subtract other end

#==(other) ⇒ Object

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

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

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

Related: #eql?, #normalize



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

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

#===(other) ⇒ Object

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

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

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



471
472
473
474
475
# File 'lib/net/imap/sequence_set.rb', line 471

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

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

:call-seq:

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

Returns a number or a subset from 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


1088
1089
1090
1091
1092
1093
# File 'lib/net/imap/sequence_set.rb', line 1088

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

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

:call-seq:

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

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

other may be any object that would be accepted by ::new: 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)).



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

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

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

:call-seq:

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

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

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

Related: #add?, #merge, #union



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

def add(object)
  tuple_add input_to_tuple object
  normalize!
end

#add?(object) ⇒ Boolean

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

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?

Returns:

  • (Boolean)


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

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

#append(object) ⇒ 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.



684
685
686
687
688
689
690
# File '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) ⇒ Object

: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



1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
# File 'lib/net/imap/sequence_set.rb', line 1037

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

#clearObject

Removes all elements and returns self.



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

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

#complement!Object

:call-seq: complement! -> self

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

Related: #complement



1168
1169
1170
1171
1172
1173
1174
1175
1176
# File 'lib/net/imap/sequence_set.rb', line 1168

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

#countObject Also known as: size

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.



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

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

#cover?(other) ⇒ Boolean

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

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

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

Returns:

  • (Boolean)


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

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

#delete(object) ⇒ Object

:call-seq: 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



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

def delete(object)
  tuple_subtract input_to_tuple object
  normalize!
end

#delete?(object) ⇒ Boolean

:call-seq:

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

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

Returns an integer when the specified number argument was removed:

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

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

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

And returns a new SequenceSet when a range is specified:

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

#string will be regenerated after deletion.

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

Returns:

  • (Boolean)


749
750
751
752
753
754
755
756
757
758
759
760
761
762
# File '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) ⇒ Object

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

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

#string will be regenerated after deletion.

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



772
773
774
# File 'lib/net/imap/sequence_set.rb', line 772

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

#disjoint?(other) ⇒ Boolean

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

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

Related: #intersection, #intersect?

Returns:

  • (Boolean)


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

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

#each_elementObject

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

The returned numbers are sorted and de-duplicated, even when the input #string is not. See #normalize.

Related: #elements, #each_entry



924
925
926
927
928
# File '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(&block) ⇒ Object

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

The entries are yielded in the same order they appear in #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



910
911
912
913
914
915
# File 'lib/net/imap/sequence_set.rb', line 910

def each_entry(&block) # :yields: integer or range or :*
  return to_enum(__method__) unless block_given?
  return each_element(&block) unless @string
  @string.split(",").each do yield tuple_to_entry str_to_tuple _1 end
  self
end

#each_number(&block) ⇒ Object

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

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

Related: #numbers

Raises:

  • (RangeError)


964
965
966
967
968
969
970
971
972
973
974
# File 'lib/net/imap/sequence_set.rb', line 964

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_rangeObject

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

Related: #ranges



946
947
948
949
950
951
952
953
954
955
# File 'lib/net/imap/sequence_set.rb', line 946

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

#elementsObject Also known as: to_a

Returns an array of ranges and integers and :*.

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

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



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

def elements; each_element.to_a end

#empty?Boolean

Returns true if the set contains no elements

Returns:

  • (Boolean)


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

def empty?; @tuples.empty? end

#entriesObject

Returns an array of ranges and integers and :*.

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

Related: #each_entry, #elements



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

def entries; each_entry.to_a end

#eql?(other) ⇒ Boolean

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

Hash equality requires the same encoded #string representation.

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

Related: #==, #normalize

Returns:

  • (Boolean)


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

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

#find_index(number) ⇒ Object

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

Related: #[]



1000
1001
1002
1003
1004
1005
1006
1007
# File 'lib/net/imap/sequence_set.rb', line 1000

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

#freezeObject

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



418
419
420
421
422
423
# File 'lib/net/imap/sequence_set.rb', line 418

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

#full?Boolean

Returns true if the set contains every possible element.

Returns:

  • (Boolean)


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

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

#hashObject

See #eql?



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

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

Returns:

  • (Boolean)


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

def include?(element) include_tuple? input_to_tuple element end

#include_star?Boolean

Returns true when the set contains *.

Returns:

  • (Boolean)


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

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

#inspectObject



1214
1215
1216
1217
1218
1219
1220
1221
1222
# File 'lib/net/imap/sequence_set.rb', line 1214

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?

Returns:

  • (Boolean)


523
524
525
# File 'lib/net/imap/sequence_set.rb', line 523

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

#limit(max:) ⇒ Object

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

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

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

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


1141
1142
1143
1144
1145
1146
1147
1148
# File 'lib/net/imap/sequence_set.rb', line 1141

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

#limit!(max:) ⇒ Object

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

Related: #limit



1154
1155
1156
1157
1158
1159
1160
# File 'lib/net/imap/sequence_set.rb', line 1154

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: :*) ⇒ Object

:call-seq: max(star: :*) => integer or star or nil

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



543
544
545
# File 'lib/net/imap/sequence_set.rb', line 543

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

#merge(*inputs) ⇒ Object

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



805
806
807
808
# File 'lib/net/imap/sequence_set.rb', line 805

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

#min(star: :*) ⇒ Object

:call-seq: min(star: :*) => integer or star or nil

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



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

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

#minmax(star: :*) ⇒ Object

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

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



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

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

#normalizeObject

Returns a new SequenceSet with a normalized string representation.

The returned set’s #string is sorted and deduplicated. Adjacent or overlapping elements will be merged into a single larger range.

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

Related: #normalize!, #normalized_string



1187
1188
1189
1190
1191
# File 'lib/net/imap/sequence_set.rb', line 1187

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

#normalize!Object

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

Related: #normalize, #normalized_string



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

def normalize!
  @string = nil
  self
end

#normalized_stringObject

Returns a normalized sequence-set string representation, sorted and deduplicated. Adjacent or overlapping elements will be merged into a single larger range. 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



1210
1211
1212
# File 'lib/net/imap/sequence_set.rb', line 1210

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

#numbersObject

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

The returned numbers are sorted and de-duplicated, even when the input #string is not. See #normalize.

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



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

def numbers; each_number.to_a end

#rangesObject

Returns an array of ranges

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

* 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



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

def ranges; each_range.to_a end

#replace(other) ⇒ Object

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

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



358
359
360
361
362
363
364
365
# File '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

#send_data(imap, tag) ⇒ Object

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



1234
1235
1236
# File 'lib/net/imap/sequence_set.rb', line 1234

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

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

:call-seq:

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

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

#string will be regenerated after deletion.

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



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

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

#stringObject

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

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

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

Related: #valid_string, #normalized_string, #to_s



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

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

#string=(str) ⇒ Object

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



399
400
401
402
403
404
405
406
407
408
# File '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) ⇒ Object

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



819
820
821
822
# File 'lib/net/imap/sequence_set.rb', line 819

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

#to_sObject

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

Related: #valid_string, #normalized_string, #to_s



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

def to_s; string || "" end

#to_setObject

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

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

See #numbers for the warning about very large sets.

Related: #elements, #ranges, #numbers



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

def to_set; Set.new(numbers) end

#valid?Boolean

Returns false when the set is empty.

Returns:

  • (Boolean)


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

def valid?; !empty? end

#valid_stringObject

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

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

Related: #string, #normalized_string, #to_s

Raises:



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

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

#validateObject

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



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

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

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

:call-seq:

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

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

other may be any object that would be accepted by ::new: 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



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

def |(other) remain_frozen dup.merge other end

#~Object Also known as: complement

:call-seq:

~ self     -> sequence set
complement -> sequence set

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

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

Related: #complement!



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

def ~; remain_frozen dup.complement! end