class Net::IMAP::SequenceSet
An IMAP sequence set is a set of message sequence numbers or unique identifier numbers (“UIDs”). It contains numbers and ranges of numbers. The numbers are all non-zero unsigned 32-bit integers and one special value ("*"
) that represents the largest value in the mailbox.
Certain types of IMAP responses will contain a SequenceSet
, for example the data for a "MODIFIED"
ResponseCode
. Some IMAP commands may receive a SequenceSet
as an argument, for example IMAP#search
, IMAP#fetch
, and IMAP#store
.
EXPERIMENTAL API¶ ↑
SequenceSet
is currently experimental. Only two methods, ::[]
and valid_string
, are considered stable. Although the API isn’t expected to change much, any other methods may be removed or changed without deprecation.
Creating sequence sets¶ ↑
SequenceSet.new
with no arguments creates an empty sequence set. Note that an empty sequence set is invalid in the IMAP grammar.
set = Net::IMAP::SequenceSet.new set.empty? #=> true set.valid? #=> false set.valid_string #!> raises DataFormatError set << 1..10 set.empty? #=> false set.valid? #=> true set.valid_string #=> "1:10"
SequenceSet.new
may receive a single optional argument: a non-zero 32 bit unsigned integer, a range, a sequence-set
formatted string, another sequence set, or an enumerable containing any of these.
set = Net::IMAP::SequenceSet.new(1) set.valid_string #=> "1" set = Net::IMAP::SequenceSet.new(1..100) set.valid_string #=> "1:100" set = Net::IMAP::SequenceSet.new(1...100) set.valid_string #=> "1:99" set = Net::IMAP::SequenceSet.new([1, 2, 5..]) set.valid_string #=> "1:2,5:*" set = Net::IMAP::SequenceSet.new("1,2,3:7,5,6:10,2048,1024") set.valid_string #=> "1,2,3:7,5,6:10,2048,1024" set = Net::IMAP::SequenceSet.new(1, 2, 3..7, 5, 6..10, 2048, 1024) set.valid_string #=> "1:10,55,1024:2048"
Use ::[]
with one or more arguments to create a frozen SequenceSet
. An invalid (empty) set cannot be created with ::[]
.
set = Net::IMAP::SequenceSet["1,2,3:7,5,6:10,2048,1024"] set.valid_string #=> "1,2,3:7,5,6:10,2048,1024" set = Net::IMAP::SequenceSet[1, 2, [3..7, 5], 6..10, 2048, 1024] set.valid_string #=> "1:10,55,1024:2048"
Normalized form¶ ↑
When a sequence set is created with a single String value, that string
representation is preserved. SequenceSet’s internal representation implicitly sorts all entries, de-duplicates numbers, and coalesces adjacent or overlapping ranges. Most enumeration methods and offset-based methods use this normalized representation. Most modification methods will convert string
to its normalized form.
In some cases the order of the string representation is significant, such as the ESORT
, CONTEXT=SORT
, and UIDPLUS
extensions. Use entries
or each_entry
to enumerate the set in its original order. To preserve string
order while modifying a set, use append
, string=
, or replace
.
Using *
¶ ↑
IMAP sequence sets may contain a special value "*"
, which represents the largest number in use. From seq-number
in RFC9051 §9:
In the case of message sequence numbers, it is the number of messages in a non-empty mailbox. In the case of unique identifiers, it is the unique identifier of the last message in the mailbox or, if the mailbox is empty, the mailbox’s current UIDNEXT value.
When creating a SequenceSet
, *
may be input as -1
, "*"
, :*
, an endless range, or a range ending in -1
. When converting to elements
, ranges
, or numbers
, it will output as either :*
or an endless range. For example:
Net::IMAP::SequenceSet["1,3,*"].to_a #=> [1, 3, :*] Net::IMAP::SequenceSet["1,234:*"].to_a #=> [1, 234..] Net::IMAP::SequenceSet[1234..-1].to_a #=> [1234..] Net::IMAP::SequenceSet[1234..].to_a #=> [1234..] Net::IMAP::SequenceSet[1234..].to_s #=> "1234:*" Net::IMAP::SequenceSet[1234..-1].to_s #=> "1234:*"
Use limit
to convert "*"
to a maximum value. When a range includes "*"
, the maximum value will always be matched:
Net::IMAP::SequenceSet["9999:*"].limit(max: 25) #=> Net::IMAP::SequenceSet["25"]
Surprising *
behavior¶ ↑
When a set includes *
, some methods may have surprising behavior.
For example, complement
treats *
as its own number. This way, the intersection
of a set and its complement
will always be empty. This is not how an IMAP server interprets the set: it will convert *
to either the number of messages in the mailbox or UIDNEXT
, as appropriate. And there will be overlap between a set and its complement after limit
is applied to each:
~Net::IMAP::SequenceSet["*"] == Net::IMAP::SequenceSet[1..(2**32-1)] ~Net::IMAP::SequenceSet[1..5] == Net::IMAP::SequenceSet["6:*"] set = Net::IMAP::SequenceSet[1..5] (set & ~set).empty? => true (set.limit(max: 4) & (~set).limit(max: 4)).to_a => [4]
When counting the number of numbers in a set, *
will be counted except when UINT32_MAX
is also in the set:
UINT32_MAX = 2**32 - 1 Net::IMAP::SequenceSet["*"].count => 1 Net::IMAP::SequenceSet[1..UINT32_MAX - 1, :*].count => UINT32_MAX Net::IMAP::SequenceSet["1:*"].count => UINT32_MAX Net::IMAP::SequenceSet[UINT32_MAX, :*].count => 1 Net::IMAP::SequenceSet[UINT32_MAX..].count => 1
What’s here?¶ ↑
SequenceSet
provides methods for:
Methods for Creating a SequenceSet¶ ↑
-
::[]
: Creates a validated frozen sequence set from one or more inputs. -
::new
: Creates a new mutable sequence set, which may be empty (invalid). -
::try_convert
: Callsto_sequence_set
on an object and verifies that the result is aSequenceSet
. -
::empty
: Returns a frozen empty (invalid)SequenceSet
. -
::full
: Returns a frozenSequenceSet
containing every possible number.
Methods for Comparing¶ ↑
Comparison to another SequenceSet:
-
==
: Returns whether a given set contains the same numbers asself
. -
eql?
: Returns whether a given set uses the samestring
asself
.
Comparison to objects which are convertible to SequenceSet:
-
===
: Returns whether a given object is fully contained withinself
, ornil
if the object cannot be converted to a compatible type. -
cover?
(aliased as===
): Returns whether a given object is fully contained withinself
. -
intersect?
(aliased asoverlap?
): Returns whetherself
and a given object have any common elements. -
disjoint?
: Returns whetherself
and a given object have no common elements.
Methods for Querying¶ ↑
These methods do not modify self
.
Set membership:
-
include?
(aliased asmember?
): 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 returnsself
. -
elements
(aliased asto_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 returnsself
. -
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 returnsself
. -
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 returnsself
. -
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 thenumbers
in the set.
Methods for Set Operations¶ ↑
These methods do not modify self
.
-
#| (aliased as
union
and+
): Returns a new set combining all members fromself
with all members from the other object. -
#& (aliased as
intersection
): Returns a new set containing all members common toself
and the other object. -
-
(aliased asdifference
): Returns a copy ofself
with all members in the other object removed. -
#^ (aliased as
xor
): Returns a new set containing all members fromself
and the other object except those common to both. -
#~ (aliased as
complement
): Returns a new set containing all members that are not inself
-
limit
: Returns a copy ofself
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; returnsself
. -
add?
: If the given object is not an element in the set, adds it and returnsself
; otherwise, returnsnil
. -
merge
: Merges multiple elements into the set; returnsself
. -
append
: Adds a given object to the set, appending it to the existing string, and returnsself
. -
string=
: Assigns a newstring
value and replaceselements
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 owncomplement
.
Methods for Deleting¶ ↑
These methods remove elements from self
.
-
clear
: Removes all elements in the set; returnsself
. -
delete
: Removes a given object from the set; returnsself
. -
delete?
: If the given object is an element in the set, removes it and returns it; otherwise, returnsnil
. -
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; returnsself
. -
limit!
: Replaces*
with a given maximum value and removes all members over that maximum; returnsself
.
Methods for IMAP String Formatting¶ ↑
-
to_s
: Returns thesequence-set
string, or an empty string when the set is empty. -
string
: Returns thesequence-set
string, or nil when empty. -
valid_string
: Returns thesequence-set
string, or raisesDataFormatError
when the set is empty. -
normalized_string
: Returns asequence-set
string with its elements sorted and coalesced, or nil when the set is empty. -
normalize
: Returns a new set with this set’s normalizedsequence-set
representation. -
normalize!
: Updatesstring
to its normalizedsequence-set
representation and returnsself
.
Constants
- COERCIBLE
- EMPTY
intentionally defined after the class implementation
- ENUMABLE
- FULL
- STARS
valid inputs for “*”
- STAR_INT
represents “*” internally, to simplify sorting (etc)
- UINT32_MAX
The largest possible non-zero unsigned 32-bit integer
Public Class Methods
Returns a frozen SequenceSet
, constructed from values
.
An empty SequenceSet
is invalid and will raise a DataFormatError
.
Use ::new
to create a mutable or empty SequenceSet
.
# File net-imap-0.4.9.1/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
Returns a frozen empty set singleton. Note that valid IMAP sequence sets cannot be empty, so this set is invalid.
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 336 def empty; EMPTY end
Returns a frozen full set singleton: "1:*"
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 339 def full; FULL end
Create a new SequenceSet
object from input
, which may be another SequenceSet
, an IMAP
formatted sequence-set
string, a number, a range, :*
, or an enumerable of these.
Use ::[]
to create a frozen (non-empty) SequenceSet
.
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 348 def initialize(input = nil) input ? replace(input) : clear end
If obj
is a SequenceSet
, returns obj
. If obj
responds_to to_sequence_set
, calls obj.to_sequence_set
and returns the result. Otherwise returns nil
.
If obj.to_sequence_set
doesn’t return a SequenceSet
, an exception is raised.
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 326 def try_convert(obj) return obj if obj.is_a?(SequenceSet) return nil unless respond_to?(:to_sequence_set) obj = obj.to_sequence_set return obj if obj.is_a?(SequenceSet) raise DataFormatError, "invalid object returned from to_sequence_set" end
Public Instance Methods
Returns a new sequence set containing only the numbers common to this set and other
.
other
may be any object that would be accepted by ::new
: a non-zero 32 bit unsigned integer, range, sequence-set
formatted string, another sequence set, or an enumerable containing any of these.
Net::IMAP::SequenceSet[1..5] & [2, 4, 6] #=> Net::IMAP::SequenceSet["2,4"]
(seqset & other)
is equivalent to (seqset - ~other)
.
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 623 def &(other) remain_frozen dup.subtract SequenceSet.new(other).complement! end
Returns a new sequence set built by duplicating this set and removing every number that appears in other
.
other
may be any object that would be accepted by ::new
: a non-zero 32 bit unsigned integer, range, sequence-set
formatted string, another sequence set, or an enumerable containing any of these.
Net::IMAP::SequenceSet[1..5] - 2 - 4 - 6 #=> Net::IMAP::SequenceSet["1,3,5"]
Related: subtract
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 605 def -(other) remain_frozen dup.subtract other end
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
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 441 def ==(other) self.class == other.class && (to_s == other.to_s || tuples == other.tuples) end
Returns whether other
is contained within the set. Returns nil
if a StandardError is raised while converting other
to a comparable type.
Related: cover?
, include?
, include_star?
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 471 def ===(other) cover?(other) rescue nil end
Returns a number or a subset from self
, without modifying the set.
When an Integer argument index
is given, the number at offset index
is returned:
set = Net::IMAP::SequenceSet["10:15,20:23,26"] set[0] #=> 10 set[5] #=> 15 set[10] #=> 26
If index
is negative, it counts relative to the end of self
:
set = Net::IMAP::SequenceSet["10:15,20:23,26"] set[-1] #=> 26 set[-3] #=> 22 set[-6] #=> 15
If index
is out of range, nil
is returned.
set = Net::IMAP::SequenceSet["10:15,20:23,26"] set[11] #=> nil set[-12] #=> nil
The result is based on the normalized set—sorted and de-duplicated—not on the assigned value of string
.
set = Net::IMAP::SequenceSet["12,20:23,11:16,21"] set[0] #=> 11 set[-1] #=> 23
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 1080 def [](index, length = nil) if length then slice_length(index, length) elsif index.is_a?(Range) then slice_range(index) else at(index) end end
Returns a new sequence set containing numbers that are exclusive between this set and other
.
other
may be any object that would be accepted by ::new
: a non-zero 32 bit unsigned integer, range, sequence-set
formatted string, another sequence set, or an enumerable containing any of these.
Net::IMAP::SequenceSet[1..5] ^ [2, 4, 6] #=> Net::IMAP::SequenceSet["1,3,5:6"]
(seqset ^ other)
is equivalent to ((seqset | other) - (seqset & other))
.
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 644 def ^(other) remain_frozen (self | other).subtract(self & other) end
Adds a range or number to the set and returns self
.
string
will be regenerated. Use merge
to add many elements at once.
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 674 def add(object) tuple_add input_to_tuple object normalize! end
Adds a range or number to the set and returns self
. Returns nil
when the object is already included in the set.
string
will be regenerated. Use merge
to add many elements at once.
Related: add
, merge
, union
, include?
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 700 def add?(object) add object unless include? object end
Adds a range or number to the set and returns self
.
Unlike add
, merge
, or union
, the new value is appended to string
. This may result in a string
which has duplicates or is out-of-order.
# File net-imap-0.4.9.1/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
Returns a number from self
, without modifying the set. Behaves the same as []
, except that at
only allows a single integer argument.
Related: []
, slice
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 1029 def at(index) index = Integer(index.to_int) if index.negative? reverse_each_tuple_with_index do |min, max, idx_min, idx_max| idx_min <= index and return from_tuple_int(min + (index - idx_min)) end else each_tuple_with_index do |min, _, idx_min, idx_max| index <= idx_max and return from_tuple_int(min + (index - idx_min)) end end nil end
Removes all elements and returns self.
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 351 def clear; @tuples, @string = [], nil; self end
Converts the SequenceSet
to its own complement
. It will contain all possible values except for those currently in the set.
Related: complement
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 1156 def complement! return replace(self.class.full) if empty? return clear if full? flat = @tuples.flat_map { [_1 - 1, _2 + 1] } if flat.first < 1 then flat.shift else flat.unshift 1 end if STAR_INT < flat.last then flat.pop else flat.push STAR_INT end @tuples = flat.each_slice(2).to_a normalize! end
Returns the count of numbers
in the set.
If *
and 2**32 - 1
(the maximum 32-bit unsigned integer value) are both in the set, they will only be counted once.
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 985 def count @tuples.sum(@tuples.count) { _2 - _1 } + (include_star? && include?(UINT32_MAX) ? -1 : 0) end
Returns whether other
is contained within the set. other
may be any object that would be accepted by ::new
.
Related: ===
, include?
, include_star?
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 483 def cover?(other) input_to_tuples(other).none? { !include_tuple?(_1) } end
Deletes the given range or number from the set and returns self
.
string
will be regenerated after deletion. Use subtract
to remove many elements at once.
Related: delete?
, delete_at
, subtract
, difference
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 712 def delete(object) tuple_subtract input_to_tuple object normalize! end
Removes a specified value from the set, and returns the removed value. Returns nil
if nothing was removed.
Returns an integer when the specified number
argument was removed:
set = Net::IMAP::SequenceSet.new [5..10, 20] set.delete?(7) #=> 7 set #=> #<Net::IMAP::SequenceSet "5:6,8:10,20"> set.delete?("20") #=> 20 set #=> #<Net::IMAP::SequenceSet "5:6,8:10"> set.delete?(30) #=> nil
Returns :*
when *
or -1
is specified and removed:
set = Net::IMAP::SequenceSet.new "5:9,20,35,*" set.delete?(-1) #=> :* set #=> #<Net::IMAP::SequenceSet "5:9,20,35">
And returns a new SequenceSet
when a range is specified:
set = Net::IMAP::SequenceSet.new [5..10, 20] set.delete?(9..) #=> #<Net::IMAP::SequenceSet "9:10,20"> set #=> #<Net::IMAP::SequenceSet "5:8"> set.delete?(21..) #=> nil
string
will be regenerated after deletion.
Related: delete
, delete_at
, subtract
, difference
, disjoint?
# File net-imap-0.4.9.1/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
Deletes a number the set, indicated by the given index
. Returns the number that was removed, or nil
if nothing was removed.
string
will be regenerated after deletion.
Related: delete
, delete?
, slice!
, subtract
, difference
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 772 def delete_at(index) slice! Integer(index.to_int) end
Returns true
if the set and a given object have no common elements, false
otherwise.
Net::IMAP::SequenceSet["5:10"].disjoint? "7,9,11" #=> false Net::IMAP::SequenceSet["5:10"].disjoint? "11:33" #=> true
Related: intersection
, intersect?
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 535 def disjoint?(other) empty? || input_to_tuples(other).none? { intersect_tuple? _1 } end
Yields each number or range (or :*
) in elements
to the block and returns self. Returns an enumerator when called without a block.
The returned numbers are sorted and de-duplicated, even when the input string
is not. See normalize
.
Related: elements
, each_entry
# File net-imap-0.4.9.1/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
Yields each number or range in string
to the block and returns self
. Returns an enumerator when called without a block.
The entries are yielded in the same order they appear in tring, with no sorting, deduplication, or coalescing. When string
is in its normalized form, this will yield the same values as each_element
.
Related: entries
, each_element
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 910 def each_entry(&block) return to_enum(__method__) unless block_given? return each_element(&block) unless @string @string.split(",").each do yield tuple_to_entry str_to_tuple _1 end self end # Yields each number or range (or <tt>:*</tt>) in #elements to the block # and returns self. Returns an enumerator when called without a block. # # The returned numbers are sorted and de-duplicated, even when the input # #string is not. See #normalize. # # Related: #elements, #each_entry def each_element # :yields: integer or range or :* return to_enum(__method__) unless block_given? @tuples.each do yield tuple_to_entry _1 end self end private def tuple_to_entry((min, max)) if min == STAR_INT then :* elsif max == STAR_INT then min.. elsif min == max then min else min..max end end # Yields each range in #ranges to the block and returns self. # Returns an enumerator when called without a block. # # Related: #ranges def each_range # :yields: range return to_enum(__method__) unless block_given? @tuples.each do |min, max| if min == STAR_INT then yield :*.. elsif max == STAR_INT then yield min.. else yield min..max end end self end # Yields each number in #numbers to the block and returns self. # If the set contains a <tt>*</tt>, RangeError will be raised. # # Returns an enumerator when called without a block (even if the set # contains <tt>*</tt>). # # Related: #numbers def each_number(&block) # :yields: integer return to_enum(__method__) unless block_given? raise RangeError, '%s contains "*"' % [self.class] if include_star? each_element do |elem| case elem when Range then elem.each(&block) when Integer then block.(elem) end end self end # Returns a Set with all of the #numbers in the sequence set. # # If the set contains a <tt>*</tt>, RangeError will be raised. # # See #numbers for the warning about very large sets. # # Related: #elements, #ranges, #numbers def to_set; Set.new(numbers) end # Returns the count of #numbers in the set. # # If <tt>*</tt> and <tt>2**32 - 1</tt> (the maximum 32-bit unsigned # integer value) are both in the set, they will only be counted once. def count @tuples.sum(@tuples.count) { _2 - _1 } + (include_star? && include?(UINT32_MAX) ? -1 : 0) end alias size count # Returns the index of +number+ in the set, or +nil+ if +number+ isn't in # the set. # # Related: #[] def find_index(number) number = to_tuple_int number each_tuple_with_index do |min, max, idx_min| number < min and return nil number <= max and return from_tuple_int(idx_min + (number - min)) end nil end private def each_tuple_with_index idx_min = 0 @tuples.each do |min, max| yield min, max, idx_min, (idx_max = idx_min + (max - min)) idx_min = idx_max + 1 end idx_min end private def reverse_each_tuple_with_index idx_max = -1 @tuples.reverse_each do |min, max| yield min, max, (idx_min = idx_max - (max - min)), idx_max idx_max = idx_min - 1 end idx_max end # :call-seq: at(index) -> integer or nil # # Returns a number from +self+, without modifying the set. Behaves the # same as #[], except that #at only allows a single integer argument. # # Related: #[], #slice def at(index) index = Integer(index.to_int) if index.negative? reverse_each_tuple_with_index do |min, max, idx_min, idx_max| idx_min <= index and return from_tuple_int(min + (index - idx_min)) end else each_tuple_with_index do |min, _, idx_min, idx_max| index <= idx_max and return from_tuple_int(min + (index - idx_min)) end end nil end # :call-seq: # seqset[index] -> integer or :* or nil # slice(index) -> integer or :* or nil # seqset[start, length] -> sequence set or nil # slice(start, length) -> sequence set or nil # seqset[range] -> sequence set or nil # slice(range) -> sequence set or nil # # Returns a number or a subset from +self+, without modifying the set. # # When an Integer argument +index+ is given, the number at offset +index+ # is returned: # # set = Net::IMAP::SequenceSet["10:15,20:23,26"] # set[0] #=> 10 # set[5] #=> 15 # set[10] #=> 26 # # If +index+ is negative, it counts relative to the end of +self+: # set = Net::IMAP::SequenceSet["10:15,20:23,26"] # set[-1] #=> 26 # set[-3] #=> 22 # set[-6] #=> 15 # # If +index+ is out of range, +nil+ is returned. # # set = Net::IMAP::SequenceSet["10:15,20:23,26"] # set[11] #=> nil # set[-12] #=> nil # # The result is based on the normalized set—sorted and de-duplicated—not # on the assigned value of #string. # # set = Net::IMAP::SequenceSet["12,20:23,11:16,21"] # set[0] #=> 11 # set[-1] #=> 23 # def [](index, length = nil) if length then slice_length(index, length) elsif index.is_a?(Range) then slice_range(index) else at(index) end end alias slice :[] private def slice_length(start, length) start = Integer(start.to_int) length = Integer(length.to_int) raise ArgumentError, "length must be positive" unless length.positive? last = start + length - 1 unless start.negative? && start.abs <= length slice_range(start..last) end private def slice_range(range) first = range.begin || 0 last = range.end || -1 last -= 1 if range.exclude_end? && range.end && last != STAR_INT if (first * last).positive? && last < first SequenceSet.empty elsif (min = at(first)) max = at(last) if max == :* then self & (min..) elsif min <= max then self & (min..max) else SequenceSet.empty end end end # Returns a frozen SequenceSet with <tt>*</tt> converted to +max+, numbers # and ranges over +max+ removed, and ranges containing +max+ converted to # end at +max+. # # Net::IMAP::SequenceSet["5,10:22,50"].limit(max: 20).to_s # #=> "5,10:20" # # <tt>*</tt> is always interpreted as the maximum value. When the set # contains <tt>*</tt>, it will be set equal to the limit. # # Net::IMAP::SequenceSet["*"].limit(max: 37) # #=> Net::IMAP::SequenceSet["37"] # Net::IMAP::SequenceSet["5:*"].limit(max: 37) # #=> Net::IMAP::SequenceSet["5:37"] # Net::IMAP::SequenceSet["500:*"].limit(max: 37) # #=> Net::IMAP::SequenceSet["37"] # def limit(max:) max = to_tuple_int(max) if empty? then self.class.empty elsif !include_star? && max < min then self.class.empty elsif max(star: STAR_INT) <= max then frozen? ? self : dup.freeze else dup.limit!(max: max).freeze end end # Removes all members over +max+ and returns self. If <tt>*</tt> is a # member, it will be converted to +max+. # # Related: #limit def limit!(max:) star = include_star? max = to_tuple_int(max) tuple_subtract [max + 1, STAR_INT] tuple_add [max, max ] if star normalize! end # :call-seq: complement! -> self # # Converts the SequenceSet to its own #complement. It will contain all # possible values _except_ for those currently in the set. # # Related: #complement def complement! return replace(self.class.full) if empty? return clear if full? flat = @tuples.flat_map { [_1 - 1, _2 + 1] } if flat.first < 1 then flat.shift else flat.unshift 1 end if STAR_INT < flat.last then flat.pop else flat.push STAR_INT end @tuples = flat.each_slice(2).to_a normalize! end # Returns a new SequenceSet with a normalized string representation. # # The returned set's #string is sorted and deduplicated. Adjacent or # overlapping elements will be merged into a single larger range. # # Net::IMAP::SequenceSet["1:5,3:7,10:9,10:11"].normalize # #=> Net::IMAP::SequenceSet["1:7,9:11"] # # Related: #normalize!, #normalized_string def normalize str = normalized_string return self if frozen? && str == string remain_frozen dup.instance_exec { @string = str&.-@; self } end # Resets #string to be sorted, deduplicated, and coalesced. Returns # +self+. # # Related: #normalize, #normalized_string def normalize! @string = nil self end # Returns a normalized +sequence-set+ string representation, sorted # and deduplicated. Adjacent or overlapping elements will be merged into # a single larger range. Returns +nil+ when the set is empty. # # Net::IMAP::SequenceSet["1:5,3:7,10:9,10:11"].normalized_string # #=> "1:7,9:11" # # Related: #normalize!, #normalize def normalized_string @tuples.empty? ? nil : -@tuples.map { tuple_to_str _1 }.join(",") end def inspect if empty? (frozen? ? "%s.empty" : "#<%s empty>") % [self.class] elsif frozen? "%s[%p]" % [self.class, to_s] else "#<%s %p>" % [self.class, to_s] end end # Returns self alias to_sequence_set itself # Unstable API: currently for internal use only (Net::IMAP#validate_data) def validate # :nodoc: empty? and raise DataFormatError, "empty sequence-set is invalid" self end # Unstable API: for internal use only (Net::IMAP#send_data) def send_data(imap, tag) # :nodoc: imap.__send__(:put_string, valid_string) end protected attr_reader :tuples # :nodoc: private def remain_frozen(set) frozen? ? set.freeze : set end # frozen clones are shallow copied def initialize_clone(other) other.frozen? ? super : initialize_dup(other) end def initialize_dup(other) @tuples = other.tuples.map(&:dup) @string = other.string&.-@ super end def input_to_tuple(obj) obj = input_try_convert obj case obj when *STARS, Integer then [int = to_tuple_int(obj), int] when Range then range_to_tuple(obj) when String then str_to_tuple(obj) else raise DataFormatError, "expected number or range, got %p" % [obj] end end def input_to_tuples(obj) obj = input_try_convert obj case obj when *STARS, Integer, Range then [input_to_tuple(obj)] when String then str_to_tuples obj when SequenceSet then obj.tuples when ENUMABLE then obj.flat_map { input_to_tuples _1 } when nil then [] else raise DataFormatError, "expected nz-number, range, string, or enumerable; " \ "got %p" % [obj] end end # unlike SequenceSet#try_convert, this returns an Integer, Range, # String, Set, Array, or... any type of object. def input_try_convert(input) SequenceSet.try_convert(input) || # Integer.try_convert(input) || # ruby 3.1+ input.respond_to?(:to_int) && Integer(input.to_int) || String.try_convert(input) || input end def range_to_tuple(range) first = to_tuple_int(range.begin || 1) last = to_tuple_int(range.end || :*) last -= 1 if range.exclude_end? && range.end && last != STAR_INT unless first <= last raise DataFormatError, "invalid range for sequence-set: %p" % [range] end [first, last] end def to_tuple_int(obj) STARS.include?(obj) ? STAR_INT : nz_number(obj) end def from_tuple_int(num) num == STAR_INT ? :* : num end def tuple_to_str(tuple) tuple.uniq.map{ from_tuple_int _1 }.join(":") end def str_to_tuples(str) str.split(",", -1).map! { str_to_tuple _1 } end def str_to_tuple(str) raise DataFormatError, "invalid sequence set string" if str.empty? str.split(":", 2).map! { to_tuple_int _1 }.minmax end def include_tuple?((min, max)) range_gte_to(min)&.cover?(min..max) end def intersect_tuple?((min, max)) range = range_gte_to(min) and range.include?(min) || range.include?(max) || (min..max).cover?(range) end def tuples_add(tuples) tuples.each do tuple_add _1 end; self end def tuples_subtract(tuples) tuples.each do tuple_subtract _1 end; self end # # --|=====| |=====new tuple=====| append # ?????????-|=====new tuple=====|-|===lower===|-- insert # # |=====new tuple=====| # ---------??=======lower=======??--------------- noop # # ---------??===lower==|--|==| join remaining # ---------??===lower==|--|==|----|===upper===|-- join until upper # ---------??===lower==|--|==|--|=====upper===|-- join to upper def tuple_add(tuple) min, max = tuple lower, lower_idx = tuple_gte_with_index(min - 1) if lower.nil? then tuples << tuple elsif (max + 1) < lower.first then tuples.insert(lower_idx, tuple) else tuple_coalesce(lower, lower_idx, min, max) end end def tuple_coalesce(lower, lower_idx, min, max) return if lower.first <= min && max <= lower.last lower[0] = [min, lower.first].min lower[1] = [max, lower.last].max lower_idx += 1 return if lower_idx == tuples.count tmax_adj = lower.last + 1 upper, upper_idx = tuple_gte_with_index(tmax_adj) if upper tmax_adj < upper.first ? (upper_idx -= 1) : (lower[1] = upper.last) end tuples.slice!(lower_idx..upper_idx) end # |====tuple================| # --|====| no more 1. noop # --|====|---------------------------|====lower====|-- 2. noop # -------|======lower================|---------------- 3. split # --------|=====lower================|---------------- 4. trim beginning # # -------|======lower====????????????----------------- trim lower # --------|=====lower====????????????----------------- delete lower # # -------??=====lower===============|----------------- 5. trim/delete one # -------??=====lower====|--|====| no more 6. delete rest # -------??=====lower====|--|====|---|====upper====|-- 7. delete until # -------??=====lower====|--|====|--|=====upper====|-- 8. delete and trim def tuple_subtract(tuple) min, max = tuple lower, idx = tuple_gte_with_index(min) if lower.nil? then nil # case 1. elsif max < lower.first then nil # case 2. elsif max < lower.last then tuple_trim_or_split lower, idx, min, max else tuples_trim_or_delete lower, idx, min, max end end def tuple_trim_or_split(lower, idx, tmin, tmax) if lower.first < tmin # split tuples.insert(idx, [lower.first, tmin - 1]) end lower[0] = tmax + 1 end def tuples_trim_or_delete(lower, lower_idx, tmin, tmax) if lower.first < tmin # trim lower lower[1] = tmin - 1 lower_idx += 1 end if tmax == lower.last # case 5 upper_idx = lower_idx elsif (upper, upper_idx = tuple_gte_with_index(tmax + 1)) upper_idx -= 1 # cases 7 and 8 upper[0] = tmax + 1 if upper.first <= tmax # case 8 (else case 7) end tuples.slice!(lower_idx..upper_idx) end def tuple_gte_with_index(num) idx = tuples.bsearch_index { _2 >= num } and [tuples[idx], idx] end def range_gte_to(num) first, last = tuples.bsearch { _2 >= num } first..last if first end def nz_number(num) case num when Integer, /\A[1-9]\d*\z/ then num = Integer(num) else raise DataFormatError, "%p is not a valid nz-number" % [num] end NumValidator.ensure_nz_number(num) num end # intentionally defined after the class implementation EMPTY = new.freeze FULL = self["1:*"] private_constant :EMPTY, :FULL end
Yields each number in numbers
to the block and returns self. If the set contains a *
, RangeError will be raised.
Returns an enumerator when called without a block (even if the set contains *
).
Related: numbers
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 960 def each_number(&block) # :yields: integer return to_enum(__method__) unless block_given? raise RangeError, '%s contains "*"' % [self.class] if include_star? each_element do |elem| case elem when Range then elem.each(&block) when Integer then block.(elem) end end self end
Yields each range in ranges
to the block and returns self. Returns an enumerator when called without a block.
Related: ranges
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 942 def each_range # :yields: range return to_enum(__method__) unless block_given? @tuples.each do |min, max| if min == STAR_INT then yield :*.. elsif max == STAR_INT then yield min.. else yield min..max end end self end
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 1005 def each_tuple_with_index idx_min = 0 @tuples.each do |min, max| yield min, max, idx_min, (idx_max = idx_min + (max - min)) idx_min = idx_max + 1 end idx_min end
Returns an array of ranges and integers and :*
.
The returned elements are sorted and coalesced, even when the input string
is not. *
will sort last. See normalize
.
By itself, *
translates to :*
. A range containing *
translates to an endless range. Use limit
to translate both cases to a maximum value.
If the original input was unordered or contains overlapping ranges, the returned ranges will be ordered and coalesced.
Net::IMAP::SequenceSet["2,5:9,6,*,12:11"].elements #=> [2, 5..9, 11..12, :*]
Related: each_element
, ranges
, numbers
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 851 def elements; each_element.to_a end
Returns true if the set contains no elements
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 565 def empty?; @tuples.empty? end
Returns an array of ranges and integers and :*
.
The entries are in the same order they appear in string
, with no sorting, deduplication, or coalescing. When string
is in its normalized form, this will return the same result as elements
. This is useful when the given order is significant, for example in a ESEARCH response to IMAP#sort
.
Related: each_entry
, elements
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 833 def entries; each_entry.to_a end
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
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 460 def eql?(other) self.class == other.class && string == other.string end
Returns the index of number
in the set, or nil
if number
isn’t in the set.
Related: []
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 996 def find_index(number) number = to_tuple_int number each_tuple_with_index do |min, max, idx_min| number < min and return nil number <= max and return from_tuple_int(idx_min + (number - min)) end nil end
Freezes and returns the set. A frozen SequenceSet
is Ractor-safe.
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 418 def freeze return self if frozen? string @tuples.each(&:freeze).freeze super end
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 1292 def from_tuple_int(num) num == STAR_INT ? :* : num end
Returns true if the set contains every possible element.
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 568 def full?; @tuples == [[1, STAR_INT]] end
See eql?
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 463 def hash; [self.class, string].hash end
Returns true
when a given number or range is in self
, and false
otherwise. Returns false
unless number
is an Integer, Range, or *
.
set = Net::IMAP::SequenceSet["5:10,100,111:115"] set.include? 1 #=> false set.include? 5..10 #=> true set.include? 11..20 #=> false set.include? 100 #=> true set.include? 6 #=> true, covered by "5:10" set.include? 4..9 #=> true, covered by "5:10" set.include? "4:9" #=> true, strings are parsed set.include? 4..9 #=> false, intersection is not sufficient set.include? "*" #=> false, use #limit to re-interpret "*" set.include? -1 #=> false, -1 is interpreted as "*" set = Net::IMAP::SequenceSet["5:10,100,111:*"] set.include? :* #=> true set.include? "*" #=> true set.include? -1 #=> true set.include? 200.. #=> true set.include? 100.. #=> false
Related: include_star?
, cover?
, ===
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 509 def include?(element) include_tuple? input_to_tuple element end
Returns true
when the set contains *
.
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 514 def include_star?; @tuples.last&.last == STAR_INT end
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 1301 def include_tuple?((min, max)) range_gte_to(min)&.cover?(min..max) end
frozen clones are shallow copied
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 1235 def initialize_clone(other) other.frozen? ? super : initialize_dup(other) end
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 1239 def initialize_dup(other) @tuples = other.tuples.map(&:dup) @string = other.string&.-@ super end
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 1245 def input_to_tuple(obj) obj = input_try_convert obj case obj when *STARS, Integer then [int = to_tuple_int(obj), int] when Range then range_to_tuple(obj) when String then str_to_tuple(obj) else raise DataFormatError, "expected number or range, got %p" % [obj] end end
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 1256 def input_to_tuples(obj) obj = input_try_convert obj case obj when *STARS, Integer, Range then [input_to_tuple(obj)] when String then str_to_tuples obj when SequenceSet then obj.tuples when ENUMABLE then obj.flat_map { input_to_tuples _1 } when nil then [] else raise DataFormatError, "expected nz-number, range, string, or enumerable; " \ "got %p" % [obj] end end
unlike SequenceSet#try_convert, this returns an Integer, Range, String, Set, Array, or… any type of object.
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 1273 def input_try_convert(input) SequenceSet.try_convert(input) || # Integer.try_convert(input) || # ruby 3.1+ input.respond_to?(:to_int) && Integer(input.to_int) || String.try_convert(input) || input end
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 1202 def inspect if empty? (frozen? ? "%s.empty" : "#<%s empty>") % [self.class] elsif frozen? "%s[%p]" % [self.class, to_s] else "#<%s %p>" % [self.class, to_s] end end
Returns true
if the set and a given object have any common elements, false
otherwise.
Net::IMAP::SequenceSet["5:10"].intersect? "7,9,11" #=> true Net::IMAP::SequenceSet["5:10"].intersect? "11:33" #=> false
Related: intersection
, disjoint?
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 523 def intersect?(other) valid? && input_to_tuples(other).any? { intersect_tuple? _1 } end
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 1303 def intersect_tuple?((min, max)) range = range_gte_to(min) and range.include?(min) || range.include?(max) || (min..max).cover?(range) end
Returns a frozen SequenceSet
with *
converted to max
, numbers and ranges over max
removed, and ranges containing max
converted to end at max
.
Net::IMAP::SequenceSet["5,10:22,50"].limit(max: 20).to_s #=> "5,10:20"
*
is always interpreted as the maximum value. When the set contains *
, it will be set equal to the limit.
Net::IMAP::SequenceSet["*"].limit(max: 37) #=> Net::IMAP::SequenceSet["37"] Net::IMAP::SequenceSet["5:*"].limit(max: 37) #=> Net::IMAP::SequenceSet["5:37"] Net::IMAP::SequenceSet["500:*"].limit(max: 37) #=> Net::IMAP::SequenceSet["37"]
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 1129 def limit(max:) max = to_tuple_int(max) if empty? then self.class.empty elsif !include_star? && max < min then self.class.empty elsif max(star: STAR_INT) <= max then frozen? ? self : dup.freeze else dup.limit!(max: max).freeze end end
Removes all members over max
and returns self. If *
is a member, it will be converted to max
.
Related: limit
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 1142 def limit!(max:) star = include_star? max = to_tuple_int(max) tuple_subtract [max + 1, STAR_INT] tuple_add [max, max ] if star normalize! end
Returns the maximum value in self
, star
when the set includes *
, or nil
when the set is empty.
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 543 def max(star: :*) (val = @tuples.last&.last) && val == STAR_INT ? star : val end
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.
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 805 def merge(*inputs) tuples_add input_to_tuples inputs normalize! end
Returns the minimum value in self
, star
when the only value in the set is *
, or nil
when the set is empty.
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 551 def min(star: :*) (val = @tuples.first&.first) && val == STAR_INT ? star : val end
Returns a 2-element array containing the minimum and maximum numbers in self
, or nil
when the set is empty.
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 559 def minmax(star: :*); [min(star: star), max(star: star)] unless empty? end
Returns a new SequenceSet
with a normalized string representation.
The returned set’s string
is sorted and deduplicated. Adjacent or overlapping elements will be merged into a single larger range.
Net::IMAP::SequenceSet["1:5,3:7,10:9,10:11"].normalize #=> Net::IMAP::SequenceSet["1:7,9:11"]
Related: normalize!
, normalized_string
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 1175 def normalize str = normalized_string return self if frozen? && str == string remain_frozen dup.instance_exec { @string = str&.-@; self } end
Resets string
to be sorted, deduplicated, and coalesced. Returns self
.
Related: normalize
, normalized_string
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 1185 def normalize! @string = nil self end
Returns a normalized sequence-set
string representation, sorted and deduplicated. Adjacent or overlapping elements will be merged into a single larger range. Returns nil
when the set is empty.
Net::IMAP::SequenceSet["1:5,3:7,10:9,10:11"].normalized_string #=> "1:7,9:11"
Related: normalize!
, normalize
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 1198 def normalized_string @tuples.empty? ? nil : -@tuples.map { tuple_to_str _1 }.join(",") end
Returns a sorted array of all of the number values in the sequence set.
The returned numbers are sorted and de-duplicated, even when the input string
is not. See normalize
.
Net::IMAP::SequenceSet["2,5:9,6,12:11"].numbers #=> [2, 5, 6, 7, 8, 9, 11, 12]
If the set contains a *
, RangeError is raised. See limit
.
Net::IMAP::SequenceSet["10000:*"].numbers #!> RangeError
WARNING: Even excluding sets with *
, an enormous result can easily be created. An array with over 4 billion integers could be returned, requiring up to 32GiB of memory on a 64-bit architecture.
Net::IMAP::SequenceSet[10000..2**32-1].numbers # ...probably freezes the process for a while... #!> NoMemoryError (probably)
For safety, consider using limit
or intersection
to set an upper bound. Alternatively, use each_element
, each_range
, or even each_number
to avoid allocation of a result array.
Related: elements
, ranges
, to_set
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 900 def numbers; each_number.to_a end
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 1397 def nz_number(num) case num when Integer, /\A[1-9]\d*\z/ then num = Integer(num) else raise DataFormatError, "%p is not a valid nz-number" % [num] end NumValidator.ensure_nz_number(num) num end
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 1392 def range_gte_to(num) first, last = tuples.bsearch { _2 >= num } first..last if first end
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 1281 def range_to_tuple(range) first = to_tuple_int(range.begin || 1) last = to_tuple_int(range.end || :*) last -= 1 if range.exclude_end? && range.end && last != STAR_INT unless first <= last raise DataFormatError, "invalid range for sequence-set: %p" % [range] end [first, last] end
Returns an array of ranges
The returned elements are sorted and coalesced, even when the input string
is not. *
will sort last. See normalize
.
*
translates to an endless range. By itself, *
translates to :*..
. Use limit
to set *
to a maximum value.
The returned ranges will be ordered and coalesced, even when the input string
is not. *
will sort last. See normalize
.
Net::IMAP::SequenceSet["2,5:9,6,*,12:11"].ranges #=> [2..2, 5..9, 11..12, :*..] Net::IMAP::SequenceSet["123,999:*,456:789"].ranges #=> [123..123, 456..789, 999..]
Related: each_range
, elements
, numbers
, to_set
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 872 def ranges; each_range.to_a end
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 1232 def remain_frozen(set) frozen? ? set.freeze : set end
Replace the contents of the set with the contents of other
and returns self
.
other
may be another SequenceSet
, or it may be an IMAP
sequence-set
string, a number, a range, *
, or an enumerable of these.
# File net-imap-0.4.9.1/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
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 1014 def reverse_each_tuple_with_index idx_max = -1 @tuples.reverse_each do |min, max| yield min, max, (idx_min = idx_max - (max - min)), idx_max idx_max = idx_min - 1 end idx_max end
Deletes a number or consecutive numbers from the set, indicated by the given index
, start
and length
, or range
of offsets. Returns the number or sequence set that was removed, or nil
if nothing was removed. Arguments are interpreted the same as for slice or []
.
string
will be regenerated after deletion.
Related: slice, delete_at
, delete
, delete?
, subtract
, difference
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 789 def slice!(index, length = nil) deleted = slice(index, length) and subtract deleted deleted end
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 1089 def slice_length(start, length) start = Integer(start.to_int) length = Integer(length.to_int) raise ArgumentError, "length must be positive" unless length.positive? last = start + length - 1 unless start.negative? && start.abs <= length slice_range(start..last) end
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 1097 def slice_range(range) first = range.begin || 0 last = range.end || -1 last -= 1 if range.exclude_end? && range.end && last != STAR_INT if (first * last).positive? && last < first SequenceSet.empty elsif (min = at(first)) max = at(last) if max == :* then self & (min..) elsif min <= max then self & (min..max) else SequenceSet.empty end end end
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 1296 def str_to_tuple(str) raise DataFormatError, "invalid sequence set string" if str.empty? str.split(":", 2).map! { to_tuple_int _1 }.minmax end
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 1295 def str_to_tuples(str) str.split(",", -1).map! { str_to_tuple _1 } end
Returns the IMAP sequence-set
string representation, or nil
when the set is empty. Note that an empty set is invalid in the IMAP syntax.
Use valid_string
to raise an exception when the set is empty, or to_s
to return an empty string.
If the set was created from a single string, it is not normalized. If the set is updated the string will be normalized.
Related: valid_string
, normalized_string
, to_s
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 390 def string; @string ||= normalized_string if valid? end
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.
# File net-imap-0.4.9.1/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
Removes all of the elements that appear in any of the given objects
from the set, and returns self
.
The objects
may be any objects that would be accepted by ::new
: non-zero 32 bit unsigned integers, ranges, sequence-set
formatted strings, other sequence sets, or enumerables containing any of these.
Related: difference
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 819 def subtract(*objects) tuples_subtract input_to_tuples objects normalize! end
Returns the IMAP sequence-set
string representation, or an empty string when the set is empty. Note that an empty set is invalid in the IMAP syntax.
Related: valid_string
, normalized_string
, to_s
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 415 def to_s; string || "" end
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 1291 def to_tuple_int(obj) STARS.include?(obj) ? STAR_INT : nz_number(obj) end
–|=====| |=====new tuple=====| append ?????????-|=====new tuple=====|-|===lower===|– insert
|=====new tuple=====|
———??=======lower=======??————— noop
———??===lower==|–|==| join remaining ———??===lower==|–|==|—-|===upper===|– join until upper ———??===lower==|–|==|–|=====upper===|– join to upper
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 1321 def tuple_add(tuple) min, max = tuple lower, lower_idx = tuple_gte_with_index(min - 1) if lower.nil? then tuples << tuple elsif (max + 1) < lower.first then tuples.insert(lower_idx, tuple) else tuple_coalesce(lower, lower_idx, min, max) end end
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 1330 def tuple_coalesce(lower, lower_idx, min, max) return if lower.first <= min && max <= lower.last lower[0] = [min, lower.first].min lower[1] = [max, lower.last].max lower_idx += 1 return if lower_idx == tuples.count tmax_adj = lower.last + 1 upper, upper_idx = tuple_gte_with_index(tmax_adj) if upper tmax_adj < upper.first ? (upper_idx -= 1) : (lower[1] = upper.last) end tuples.slice!(lower_idx..upper_idx) end
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 1388 def tuple_gte_with_index(num) idx = tuples.bsearch_index { _2 >= num } and [tuples[idx], idx] end
|====tuple================|
–|====| no more 1. noop –|====|—————————|====lower====|– 2. noop ——-|======lower================|—————- 3. split ——–|=====lower================|—————- 4. trim beginning
——-|======lower====????????????—————– trim lower ——–|=====lower====????????????—————– delete lower
——-??=====lower===============|—————– 5. trim/delete one ——-??=====lower====|–|====| no more 6. delete rest ——-??=====lower====|–|====|—|====upper====|– 7. delete until ——-??=====lower====|–|====|–|=====upper====|– 8. delete and trim
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 1357 def tuple_subtract(tuple) min, max = tuple lower, idx = tuple_gte_with_index(min) if lower.nil? then nil # case 1. elsif max < lower.first then nil # case 2. elsif max < lower.last then tuple_trim_or_split lower, idx, min, max else tuples_trim_or_delete lower, idx, min, max end end
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 930 def tuple_to_entry((min, max)) if min == STAR_INT then :* elsif max == STAR_INT then min.. elsif min == max then min else min..max end end
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 1294 def tuple_to_str(tuple) tuple.uniq.map{ from_tuple_int _1 }.join(":") end
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 1367 def tuple_trim_or_split(lower, idx, tmin, tmax) if lower.first < tmin # split tuples.insert(idx, [lower.first, tmin - 1]) end lower[0] = tmax + 1 end
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 1308 def tuples_add(tuples) tuples.each do tuple_add _1 end; self end
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 1309 def tuples_subtract(tuples) tuples.each do tuple_subtract _1 end; self end
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 1374 def tuples_trim_or_delete(lower, lower_idx, tmin, tmax) if lower.first < tmin # trim lower lower[1] = tmin - 1 lower_idx += 1 end if tmax == lower.last # case 5 upper_idx = lower_idx elsif (upper, upper_idx = tuple_gte_with_index(tmax + 1)) upper_idx -= 1 # cases 7 and 8 upper[0] = tmax + 1 if upper.first <= tmax # case 8 (else case 7) end tuples.slice!(lower_idx..upper_idx) end
Returns false when the set is empty.
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 562 def valid?; !empty? end
Returns the IMAP sequence-set
string representation, or raises a DataFormatError
when the set is empty.
Use string
to return nil
or to_s
to return an empty string without error.
Related: string
, normalized_string
, to_s
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 374 def valid_string raise DataFormatError, "empty sequence-set" if empty? string end
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"]
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 586 def |(other) remain_frozen dup.merge other end
Returns the complement of self, a SequenceSet
which contains all numbers except for those in this set.
~Net::IMAP::SequenceSet.full #=> Net::IMAP::SequenceSet.empty ~Net::IMAP::SequenceSet.empty #=> Net::IMAP::SequenceSet.full ~Net::IMAP::SequenceSet["1:5,100:222"] #=> Net::IMAP::SequenceSet["6:99,223:*"] ~Net::IMAP::SequenceSet["6:99,223:*"] #=> Net::IMAP::SequenceSet["1:5,100:222"]
Related: complement!
# File net-imap-0.4.9.1/lib/net/imap/sequence_set.rb, line 662 def ~; remain_frozen dup.complement! end