File Coverage

blib/lib/Mail/IMAPClient/MessageSet.pm
Criterion Covered Total %
statement 49 50 98.0
branch 12 18 66.6
condition 1 3 33.3
subroutine 12 13 92.3
pod 2 6 33.3
total 76 90 84.4


line stmt bran cond sub pod time code
1 4     4   66950 use warnings;
  4         16  
  4         130  
2 4     4   21 use strict;
  4         7  
  4         526  
3              
4             package Mail::IMAPClient::MessageSet;
5              
6             =head1 NAME
7              
8             Mail::IMAPClient::MessageSet - ranges of message sequence numbers
9              
10             =cut
11              
12             use overload
13             '""' => "str"
14 1     1   5 , '.=' => sub {$_[0]->cat($_[1])}
15 0     0   0 , '+=' => sub {$_[0]->cat($_[1])}
16 1     1   4 , '-=' => sub {$_[0]->rem($_[1])}
17 4         41 , '@{}' => "unfold"
18 4     4   4691 , fallback => 1;
  4         3890  
19              
20             sub new
21 1     1 1 86 { my $class = shift;
22 1         5 my $range = $class->range(@_);
23 1         4 bless \$range, $class;
24             }
25              
26 3     3 0 823 sub str { overload::StrVal( ${$_[0]} ) }
  3         13  
27              
28             sub _unfold_range($)
29             # { my $x = shift; return if $x =~ m/[^0-9,:]$/; $x =~ s/\:/../g; eval $x; }
30 36 100   36   80 { map { /(\d+)\s*\:\s*(\d+)/ ? ($1..$2) : $_ }
  60         299  
31             split /\,/, shift;
32             }
33              
34             sub rem
35 1     1 0 3 { my $self = shift;
36 1         3 my %delete = map { ($_ => 1) } map { _unfold_range $_ } @_;
  2         7  
  1         2  
37 1         4 $$self = $self->range(grep {not $delete{$_}} $self->unfold);
  30         104  
38 1         5 $self;
39             }
40              
41             sub cat
42 1     1 0 2 { my $self = shift;
43 1         4 $$self = $self->range($$self, @_);
44 1         3 $self;
45             }
46              
47             sub range
48 3     3 0 6 { my $self = shift;
49              
50 3         6 my @msgs;
51 3         8 foreach my $m (@_)
52 31 50 33     98 { defined $m && length $m
53             or next;
54              
55 31 50       59 foreach my $mm (ref $m eq 'ARRAY' ? @$m : $m)
56 31         49 { push @msgs, _unfold_range $mm;
57             }
58             }
59              
60             @msgs
61 3 50       8 or return undef;
62              
63 3         13 @msgs = sort {$a <=> $b} @msgs;
  153         194  
64 3         6 my $low = my $high = shift @msgs;
65              
66 3         6 my @ranges;
67 3         5 foreach my $m (@msgs)
68 84 100       134 { next if $m == $high; # double
69              
70 75 100       116 if($m == $high + 1) { $high = $m }
  65         90  
71             else
72 10 50       26 { push @ranges, $low == $high ? $low : "$low:$high";
73 10         17 $low = $high = $m;
74             }
75             }
76              
77 3 50       9 push @ranges, $low == $high ? $low : "$low:$high" ;
78 3         17 join ",", @ranges;
79             }
80              
81             sub unfold
82 4     4 1 1079 { my $self = shift;
83 4 50       19 wantarray ? ( _unfold_range $$self ) : [ _unfold_range $$self ];
84             }
85              
86             =head1 SYNOPSIS
87              
88             my @msgs = $imap->search("SUBJECT","Virus"); # returns 1,3,4,5,6,9,10
89             my $msgset = Mail::IMAPClient::MessageSet->new(@msgs);
90             print $msgset; # prints "1,3:6,9:10"
91              
92             # add message 14 to the set:
93             $msgset += 14;
94             print $msgset; # prints "1,3:6,9:10,14"
95              
96             # add messages 16,17,18,19, and 20 to the set:
97             $msgset .= "16,17,18:20";
98             print $msgset; # prints "1,3:6,9:10,14,16:20"
99              
100             # Hey, I didn't really want message 17 in there; let's take it out:
101             $msgset -= 17;
102             print $msgset; # prints "1,3:6,9:10,14,16,18:20"
103              
104             # Now let's iterate over each message:
105             for my $msg (@$msgset)
106             { print "$msg\n"; # Prints: "1\n3\n4\n5\n6..16\n18\n19\n20\n"
107             }
108             print join("\n", @$msgset)."\n"; # same simpler
109             local $" = "\n"; print "@$msgset\n"; # even more simple
110              
111             =head1 DESCRIPTION
112              
113             The B module is designed to make life easier
114             for programmers who need to manipulate potentially large sets of IMAP
115             message UID's or sequence numbers.
116              
117             This module presents an object-oriented interface into handling your
118             message sets. The object reference returned by the L method is an
119             overloaded reference to a scalar variable that contains the message set's
120             compact RFC2060 representation. The object is overloaded so that using
121             it like a string returns this compact message set representation. You
122             can also add messages to the set (using either a '.=' operator or a '+='
123             operator) or remove messages (with the '-=' operator). And if you use
124             it as an array reference, it will humor you and act like one by calling
125             L for you.
126              
127             RFC2060 specifies that multiple messages can be provided to certain IMAP
128             commands by separating them with commas. For example, "1,2,3,4,5" would
129             specify messages 1, 2, 3, 4, and (you guessed it!) 5. However, if you are
130             performing an operation on lots of messages, this string can get quite long.
131             So long that it may slow down your transaction, and perhaps even cause the
132             server to reject it. So RFC2060 also permits you to specify a range of
133             messages, so that messages 1, 2, 3, 4 and 5 can also be specified as
134             "1:5".
135              
136             This is where B comes in. It will convert
137             your message set into the shortest correct syntax. This could potentially
138             save you tons of network I/O, as in the case where you want to fetch the
139             flags for all messages in a 10000 message folder, where the messages
140             are all numbered sequentially. Delimited as commas, and making the
141             best-case assumption that the first message is message "1", it would take
142             48893 bytes to specify the whole message set using the comma-delimited
143             method. To specify it as a range, it takes just seven bytes (1:10000).
144              
145             Note that the L B method can be used as
146             a short-cut to specifying Cnew(@etc)>.)
147              
148             =head1 CLASS METHODS
149              
150             The only class method you need to worry about is B. And if you create
151             your B objects via L's
152             B method then you don't even need to worry about B.
153              
154             =head2 new
155              
156             Example:
157              
158             my $msgset = Mail::IMAPClient::MessageSet->new(@msgs);
159              
160             The B method requires at least one argument. That argument can be
161             either a message, a comma-separated list of messages, a colon-separated
162             range of messages, or a combination of comma-separated messages and
163             colon-separated ranges. It can also be a reference to an array of messages,
164             comma-separated message lists, and colon separated ranges.
165              
166             If more then one argument is supplied to B, then those arguments should
167             be more message numbers, lists, and ranges (or references to arrays of them)
168             just as in the first argument.
169              
170             The message numbers passed to B can really be any kind of number at
171             all but to be useful in a L session they should be either
172             message UID's (if your I parameter is true) or message sequence numbers.
173              
174             The B method will return a reference to a B
175             object. That object, when double quoted, will act just like a string whose
176             value is the message set expressed in the shortest possible way, with the
177             message numbers sorted in ascending order and with duplicates removed.
178              
179             =head1 OBJECT METHODS
180              
181             The only object method currently available to a B
182             object is the L method.
183              
184             =head2 unfold
185              
186             Example:
187              
188             my $msgset = $imap->Range( $imap->messages ) ;
189             my @all_messages = $msgset->unfold;
190              
191             The B method returns an array of messages that belong to the
192             message set. If called in a scalar context it returns a reference to the
193             array instead.
194              
195             =head1 OVERRIDDEN OPERATIONS
196              
197             B overrides a number of operators in order
198             to make manipulating your message sets easier. The overridden operations are:
199              
200             =head2 stringify
201              
202             Attempts to stringify a B object will result in
203             the compact message specification being returned, which is almost certainly
204             what you will want.
205              
206             =head2 Auto-increment
207              
208             Attempts to autoincrement a B object will
209             result in a message (or messages) being added to the object's message set.
210              
211             Example:
212              
213             $msgset += 34;
214             # Message #34 is now in the message set
215              
216             =head2 Concatenate
217              
218             Attempts to concatenate to a B object will
219             result in a message (or messages) being added to the object's message set.
220              
221             Example:
222              
223             $msgset .= "34,35,36,40:45";
224             # Messages 34,35,36,40,41,42,43,44,and 45 are now in the message set
225              
226             The C<.=> operator and the C<+=> operator can be used interchangeably, but
227             as you can see by looking at the examples there are times when use of one
228             has an aesthetic advantage over use of the other.
229              
230             =head2 Autodecrement
231              
232             Attempts to autodecrement a B object will
233             result in a message being removed from the object's message set.
234              
235             Examples:
236              
237             $msgset -= 34;
238             # Message #34 is no longer in the message set
239             $msgset -= "1:10";
240             # Messages 1 through 10 are no longer in the message set
241              
242             If you attempt to remove a message that was not in the original message set
243             then your resulting message set will be the same as the original, only more
244             expensive. However, if you attempt to remove several messages from the message
245             set and some of those messages were in the message set and some were not,
246             the additional overhead of checking for the messages that were not there
247             is negligible. In either case you get back the message set you want regardless
248             of whether it was already like that or not.
249              
250             =head1 AUTHOR
251              
252             David J. Kernen
253             The Kernen Consulting Group, Inc
254              
255             =head1 COPYRIGHT
256              
257             Copyright 1999, 2000, 2001, 2002 The Kernen Group, Inc.
258             All rights reserved.
259              
260             This program is free software; you can redistribute it and/or modify it
261             under the terms of either:
262              
263             =over 4
264              
265             =item a) the "Artistic License" which comes with this Kit, or
266              
267             =item b) the GNU General Public License as published by the Free Software
268             Foundation; either version 1, or (at your option) any later version.
269              
270             =back
271              
272             This program is distributed in the hope that it will be useful, but
273             WITHOUT ANY WARRANTY; without even the implied warranty of
274             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See either the GNU
275             General Public License or the Artistic License for more details. All your
276             base are belong to us.
277              
278             =cut
279              
280             1;