File Coverage

blib/lib/IMAP/Query.pm
Criterion Covered Total %
statement 20 75 26.6
branch 0 30 0.0
condition 0 13 0.0
subroutine 7 12 58.3
pod 1 1 100.0
total 28 131 21.3


line stmt bran cond sub pod time code
1             package IMAP::Query;
2              
3 1     1   12664 use 5.006;
  1         2  
4 1     1   4 use strict;
  1         1  
  1         16  
5 1     1   3 use warnings FATAL => 'all';
  1         3  
  1         31  
6              
7 1     1   3 use Exporter qw(import);
  1         0  
  1         23  
8 1     1   453 use List::MoreUtils qw(any);
  1         7379  
  1         5  
9              
10             =head1 NAME
11              
12             IMAP::Query - Build IMAP search queries!
13              
14             =head1 VERSION
15              
16             Version 0.02
17              
18             =cut
19              
20             our $VERSION = '0.02';
21              
22 1     1   862 use Readonly;
  1         2013  
  1         262  
23             Readonly our @EXPORT_OK => qw(build_search_string);
24              
25             Readonly my @KEYWORDS => qw(
26             ALL
27             ANSWERED
28             BCC
29             BEFORE
30             BODY
31             CC
32             DELETED
33             DRAFT
34             FLAGGED
35             FROM
36             HEADER
37             KEYWORD
38             LARGER
39             NEW
40             NOT
41             OLD
42             ON
43             OR
44             RECENT
45             SEEN
46             SENTBEFORE
47             SENTON
48             SENTSINCE
49             SINCE
50             SMALLER
51             SUBJECT
52             TEXT
53             TO
54             UID
55             UNANSWERED
56             UNDELETED
57             UNDRAFT
58             UNFLAGGED
59             UNKEYWORD
60             UNSEEN
61             );
62              
63             =head1 SYNOPSIS
64              
65             This module is for those of us that can't create Polish notation queries by
66             hand, L can help you create them using a syntax inspired by other
67             query builder modules such as L.
68              
69             use IMAP::Query qw(build_search_string);
70              
71             my $query = build_search_string(
72             BEFORE => strftime('%d-%b-%Y', localtime(parsedate('yesterday'))),
73             NOT => 'DELETED',
74             -or => [
75             FROM => 'test@example.com',
76             -and => [
77             FROM => 'other@example.com',
78             SUBJECT => 'TESTING',
79             ],
80             ],
81             );
82              
83             ... # Do something interesting with our $query
84              
85             =head1 EXPORT
86              
87             A list of functions that can be exported. You can delete this section if you
88             don't export anything, such as for a purely object-oriented module.
89              
90             =head1 SUBROUTINES/METHODS
91              
92             =head2 build_search_string()
93              
94             This method can be exported via your use statement.
95              
96             use IMAP::Query qw(build_search_string);
97              
98             It takes two arguments.
99              
100             my $query = build_search_string($query, $operator);
101              
102             =over
103              
104             =item $query
105              
106             This option is requried. It is a HASHREF that contains all the items you want
107             to include in your search string.
108              
109             =item $operator
110              
111             This argument is optional and defaults to 'AND'. You can use either 'AND' or
112             'OR'.
113              
114             =back
115              
116             =cut
117              
118             sub build_search_string
119             {
120 0     0 1   my @options = @_;
121              
122 0           return _build_search_string_recurse(\@options);
123             }
124              
125             sub _build_search_string_recurse
126             {
127 0     0     my $opts = shift;
128 0   0       my $op = shift || 'AND';
129              
130 0           my @options = @$opts;
131              
132 0           my $rv = '';
133              
134 0           while (@options) {
135 0           my $item = shift @options;
136              
137 0 0 0       if (ref $item eq 'ARRAY') {
    0 0        
    0          
    0          
138 0           my $value = _build_search_string_ARRAYREF($item, $op);
139 0 0         if (length $value) {
140 0           _maybe_add_space($rv);
141 0           $rv .= $value;
142             }
143             }
144             elsif (ref $item eq 'HASH') {
145 0           _maybe_add_space($rv);
146 0           $rv .= _build_search_string_recurse([ %$item ], $op);
147             }
148             elsif (defined $item && $item =~ /^-/) {
149 0 0         if ($item =~ /^-and$/) {
    0          
150 0           $op = 'AND';
151             }
152             elsif ($item =~ /^-or$/) {
153 0           $op = 'OR';
154             }
155             else {
156 0           die "Unknown operator $item.\n";
157             }
158             }
159             elsif (defined $item && length $item) {
160 0           _maybe_add_space($rv);
161 1     1   530 use Data::Dumper;
  1         6088  
  1         307  
162 0 0   0     if (any{ uc($item) eq $_ } @KEYWORDS) {
  0            
163 0           $rv .= uc($item);
164             }
165             else {
166 0           $rv .= qq/"$item"/;
167             }
168             }
169             }
170              
171 0           return ($op, $rv);
172             }
173              
174             sub _build_search_string_ARRAYREF
175             {
176 0     0     my $array = shift;
177 0   0       my $op = shift || 'AND';
178 0           my $local_op = $op;
179              
180 0           my $rv = '';
181 0 0         return $rv unless @$array;
182              
183 0           my @items;
184 0           foreach my $item (@$array) {
185 0           my $value = '';
186 0           ($local_op, $value) = _build_search_string_recurse([$item], $local_op);
187 0 0         if (length $value) {
188 0           push(@items, $value);
189             }
190             }
191 0 0         return $rv unless @items;
192              
193 0 0         if ($op eq 'AND') {
194 0           _maybe_add_space($rv);
195 0           $rv .= '(';
196 0           foreach my $item (@items) {
197 0           _maybe_add_space($rv);
198 0           $rv .= $item;
199             }
200 0           $rv .= ')';
201 0           return $rv;
202             }
203              
204 0 0         if (@items > 1) {
205 0           do {
206 0           my $or = '(OR '.shift(@items).' '.shift(@items).')';
207 0           push(@items, $or);
208             } while (@items >= 2);
209             }
210 0 0         if (@items) {
211 0           _maybe_add_space($rv);
212 0           $rv .= $items[0];
213             }
214              
215 0           return $rv;
216             }
217              
218             sub _maybe_add_space
219             {
220 0 0 0 0     if (length $_[0] && substr($_[0], -1, 1) !~ /^[\(\s]$/) {
221 0           $_[0] .= ' ';
222             }
223             }
224              
225             =head1 AUTHOR
226              
227             Adam R. Schobelock, C<< >>
228              
229             =head1 BUGS
230              
231             Please report any bugs or feature requests to C, or through
232             the web interface at L. I will be notified, and then you'll
233             automatically be notified of progress on your bug as I make changes.
234              
235             =head1 SUPPORT
236              
237             You can find documentation for this module with the perldoc command.
238              
239             perldoc IMAP::Query
240              
241             You can also look for information at:
242              
243             =over 4
244              
245             =item * RT: CPAN's request tracker (report bugs here)
246              
247             L
248              
249             =item * AnnoCPAN: Annotated CPAN documentation
250              
251             L
252              
253             =item * CPAN Ratings
254              
255             L
256              
257             =item * Search CPAN
258              
259             L
260              
261             =item * Code Repository
262              
263             L
264              
265             =back
266              
267             =head1 ACKNOWLEDGEMENTS
268              
269             =head1 LICENSE AND COPYRIGHT
270              
271             Copyright 2015 Adam R. Schobelock.
272              
273             This program is free software; you can redistribute it and/or modify it
274             under the terms of the the Artistic License (2.0). You may obtain a
275             copy of the full license at:
276              
277             L
278              
279             Any use, modification, and distribution of the Standard or Modified
280             Versions is governed by this Artistic License. By using, modifying or
281             distributing the Package, you accept this license. Do not use, modify,
282             or distribute the Package, if you do not accept this license.
283              
284             If your Modified Version has been derived from a Modified Version made
285             by someone other than you, you are nevertheless required to ensure that
286             your Modified Version complies with the requirements of this license.
287              
288             This license does not grant you the right to use any trademark, service
289             mark, tradename, or logo of the Copyright Holder.
290              
291             This license includes the non-exclusive, worldwide, free-of-charge
292             patent license to make, have made, use, offer to sell, sell, import and
293             otherwise transfer the Package with respect to any patent claims
294             licensable by the Copyright Holder that are necessarily infringed by the
295             Package. If you institute patent litigation (including a cross-claim or
296             counterclaim) against any party alleging that the Package constitutes
297             direct or contributory patent infringement, then this Artistic License
298             to you shall terminate on the date that such litigation is filed.
299              
300             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
301             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
302             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
303             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
304             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
305             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
306             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
307             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
308              
309             =cut
310              
311             1; # End of IMAP::Query