File Coverage

blib/lib/Log/Procmail.pm
Criterion Covered Total %
statement 109 110 99.0
branch 45 52 86.5
condition 7 9 77.7
subroutine 19 19 100.0
pod 6 6 100.0
total 186 196 94.9


line stmt bran cond sub pod time code
1             package Log::Procmail;
2             $Log::Procmail::VERSION = '0.13';
3             require 5.005;
4 3     3   114973 use strict;
  3         22  
  3         76  
5 3     3   1233 use IO::File;
  3         22080  
  3         265  
6 3     3   1198 use IO::Select;
  3         4414  
  3         117  
7 3     3   17 use Carp;
  3         4  
  3         116  
8 3     3   1312 use UNIVERSAL ();
  3         50  
  3         404  
9              
10 3     3   14 use vars qw/ $VERSION /;
  3         5  
  3         2743  
11             local $^W = 1;
12              
13             $VERSION = '0.12';
14              
15             my %month;
16             @month{qw/ Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec /} = ( 0 .. 11 );
17              
18             my $DATE = qr/(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) ([ \d]\d) (\d\d):(\d\d):(\d\d) .*(\d\d\d\d)/;
19              
20             sub new {
21 10     10 1 747 my $class = shift;
22 10         53 return bless {
23             fh => new IO::File,
24             files => [@_],
25             errors => 0,
26             buffer => [],
27             }, $class;
28             }
29              
30             sub next {
31 91     91 1 16835 my $log = shift; # who needs $self?
32              
33             # try to read a record (3 lines)
34 91         157 my $fh = $log->fh();
35             READ:
36             {
37 92         105 my $read;
  96         101  
38              
39             # ensure we can read past the previous end of file
40 96 100       183 $fh->clearerr if $fh->eof;
41             LINE:
42 96         968 while (<$fh>) {
43 214         282 $read++;
44              
45             # should carp if doesn't get what's expected
46             # (From, then Subject, then Folder)
47              
48             # From create a new Abstract
49 214 100       831 /^From (.+?) +($DATE)$/o && do {
50 50         64 push @{$log->{buffer}}, Log::Procmail::Abstract->new();
  50         122  
51              
52             # assert: $read == 1;
53 50         117 $log->{buffer}[-1]->from($1);
54 50         97 $log->{buffer}[-1]->date($2);
55              
56             # return ASAP
57 50 100       52 last READ if @{$log->{buffer}} > 1;
  50         92  
58 49         153 next LINE;
59             };
60              
61             # assert: $read == 2;
62 164 100       355 /^ Subject: (.*)/i && do {
63 1         4 push @{$log->{buffer}}, Log::Procmail::Abstract->new()
64 49 100       46 unless @{$log->{buffer}};
  49         96  
65 49         111 $log->{buffer}[0]->subject($1);
66 49         117 next LINE;
67             };
68              
69             # procmail tabulates with tabs and spaces... :-(
70             # assert: $read == 3;
71             # Folder means the end of this record
72 115 100       282 /^ Folder: (.*?)\s+(\d+)$/ && do {
73 0         0 push @{$log->{buffer}}, Log::Procmail::Abstract->new()
74 50 50       53 unless @{$log->{buffer}};
  50         88  
75              
76             # assert: $read == 3;
77 50         119 $log->{buffer}[0]->folder($1);
78 50         97 $log->{buffer}[0]->size($2);
79 50         66 last READ;
80             };
81              
82             # fall through: some error message
83             # shall we ignore it?
84 65 100       125 next LINE unless $log->{errors};
85              
86             # or return it?
87 38         69 chomp;
88 38         43 push @{$log->{buffer}}, $_;
  38         84  
89 38         55 last LINE;
90             }
91              
92             # in case we couldn't read the first line
93 45 100 100     99 if ( !$read or @{$log->{buffer}} == 0 ) {
  39         119  
94              
95             # return ASAP
96 7 50       10 last READ if @{$log->{buffer}};
  7         14  
97              
98             # go to next file
99 7 100       15 redo READ if $log->_open_next;
100              
101             # unless it's the last one
102 3         9 return;
103             }
104             }
105              
106             # we have an abstract
107 89         109 my $rec = shift @{$log->{buffer}};
  89         141  
108 89 100       281 if(UNIVERSAL::isa( $rec, 'Log::Procmail::Abstract')) {
109             # the folder field is required
110 51 100       80 goto READ unless defined $rec->folder;
111 50         83 $rec->{source} = $log->{source};
112             }
113              
114 88         192 return $rec;
115             }
116              
117             sub push {
118 3     3 1 449 my $log = shift;
119 3         4 push @{ $log->{files} }, @_;
  3         7  
120             }
121              
122             sub errors {
123 6     6 1 1002 my $self = shift;
124 6 100       25 return @_ ? $self->{errors} = shift() : $self->{errors};
125             }
126              
127             sub fh {
128 95     95 1 137 my $log = shift;
129 95 100       216 $log->_open_next unless $log->{fh}->opened();
130 95         471 return $log->{fh};
131             }
132              
133             sub select {
134 3     3 1 840 my $log = shift;
135 3         7 $log->fh(); # make sure the file is correctly opened and select is updated
136 3         11 return $log->{select};
137             }
138              
139             # *internal method*
140             # opens a file or replace the old filehandle by the new one
141             # push() can therefore accept refs to typeglobs, IO::Handle, or filenames
142             sub _open_next {
143 16     16   87 my ( $log ) = @_;
144 16         18 my $file;
145              
146 16 100       17 if ( @{ $log->{files} } ) {
  16         33  
147 13         14 $file = shift @{ $log->{files} };
  13         24  
148 3         9 } else { return 0 };
149              
150 13 100 66     52 if ( ref $file eq 'GLOB' ) {
    100          
151 1         4 $log->{fh} = *$file{IO};
152 1 50       2 carp "Closed filehandle $log->{fh}" unless $log->{fh}->opened;
153             }
154             elsif ( ref $file && $file->isa('IO::Handle') ) {
155 1         3 $log->{fh} = $file;
156             }
157             else {
158 11 50       28 $log->{fh}->open($file) or carp "Can't open $file: $!";
159             }
160 13         511 $log->{source} = $file;
161             $log->{select} = ( grep $^O eq $_, qw(MSWin32 NetWare dos VMS riscos beos) )
162             ? undef
163 13 50       115 : IO::Select->new( $log->{fh} );
164 13         592 1;
165             }
166              
167             sub DESTROY {
168 10     10   1474 my $self = shift;
169 10 100 66     50 if ( $self->{fh} && $self->{fh}->opened ) { $self->{fh}->close }
  9         80  
170             }
171              
172             #
173             # a small class for the abstracts themselves
174             #
175             package Log::Procmail::Abstract;
176             $Log::Procmail::Abstract::VERSION = '0.13';
177 3     3   29 use Carp;
  3         5  
  3         292  
178              
179             sub new {
180 52     52   142 my $class = shift;
181 52         111 return bless {@_}, $class;
182             }
183              
184             for my $attr (qw( from date subject size folder source ) ) {
185 3     3   18 no strict 'refs';
  3         4  
  3         583  
186             *$attr = sub {
187 408     408   1279 my $self = shift;
188 408 100       1122 @_ ? $self->{$attr} = shift: $self->{$attr};
189             }
190             }
191              
192             sub ymd {
193 3     3   6 my $self = shift;
194 3 50       10 croak("Log::Procmail::Abstract::ymd cannot be used to set the date")
195             if @_;
196 3 50       8 return undef unless defined $self->{date};
197 3         150 $self->{date} =~ /^$DATE$/o;
198 3 100       20 return undef unless $1;
199 2         22 return sprintf( "%04d%02d%02d$3$4$5", $6, $month{$1} + 1, $2 );
200             }
201              
202             1;
203              
204             __END__