File Coverage

blib/lib/Log/Procmail.pm
Criterion Covered Total %
statement 108 109 99.0
branch 43 50 86.0
condition 7 9 77.7
subroutine 19 19 100.0
pod 6 6 100.0
total 183 193 94.8


line stmt bran cond sub pod time code
1             package Log::Procmail;
2              
3             require 5.005;
4 4     4   83200 use strict;
  4         9  
  4         154  
5 4     4   3938 use IO::File;
  4         62582  
  4         811  
6 4     4   4574 use IO::Select;
  4         8056  
  4         213  
7 4     4   164 use Carp;
  4         8  
  4         301  
8 4     4   5223 use UNIVERSAL ();
  4         60  
  4         128  
9              
10 4     4   22 use vars qw/ $VERSION /;
  4         9  
  4         5126  
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 998 my $class = shift;
22 10         79 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 25205 my $log = shift; # who needs $self?
32              
33             # try to read a record (3 lines)
34 91         189 my $fh = $log->fh();
35 96         108 READ:
36             {
37 92         112 my $read;
38              
39             LINE:
40 96         579 while (<$fh>) {
41 214         264 $read++;
42              
43             # should carp if doesn't get what's expected
44             # (From, then Subject, then Folder)
45              
46             # From create a new Abstract
47 214 100       1048 /^From (.+?) +($DATE)$/o && do {
48 50         72 push @{$log->{buffer}}, Log::Procmail::Abstract->new();
  50         192  
49              
50             # assert: $read == 1;
51 50         665 $log->{buffer}[-1]->from($1);
52 50         148 $log->{buffer}[-1]->date($2);
53              
54             # return ASAP
55 50 100       58 last READ if @{$log->{buffer}} > 1;
  50         154  
56 49         274 next LINE;
57             };
58              
59             # assert: $read == 2;
60 164 100       517 /^ Subject: (.*)/i && do {
61 1         6 push @{$log->{buffer}}, Log::Procmail::Abstract->new()
  49         149  
62 49 100       52 unless @{$log->{buffer}};
63 49         133 $log->{buffer}[0]->subject($1);
64 49         178 next LINE;
65             };
66              
67             # procmail tabulates with tabs and spaces... :-(
68             # assert: $read == 3;
69             # Folder means the end of this record
70 115 100       436 /^ Folder: (.*?)\s+(\d+)$/ && do {
71 0         0 push @{$log->{buffer}}, Log::Procmail::Abstract->new()
  50         142  
72 50 50       59 unless @{$log->{buffer}};
73              
74             # assert: $read == 3;
75 50         143 $log->{buffer}[0]->folder($1);
76 50         140 $log->{buffer}[0]->size($2);
77 50         90 last READ;
78             };
79              
80             # fall through: some error message
81             # shall we ignore it?
82 65 100       210 next LINE unless $log->{errors};
83              
84             # or return it?
85 38         51 chomp;
86 38         39 push @{$log->{buffer}}, $_;
  38         95  
87 38         55 last LINE;
88             }
89              
90             # in case we couldn't read the first line
91 45 100 100     115 if ( !$read or @{$log->{buffer}} == 0 ) {
  39         158  
92              
93             # return ASAP
94 7 50       8 last READ if @{$log->{buffer}};
  7         27  
95              
96             # go to next file
97 7 100       23 redo READ if $log->_open_next;
98              
99             # unless it's the last one
100 3         9 return;
101             }
102             }
103              
104             # we have an abstract
105 89         99 my $rec = shift @{$log->{buffer}};
  89         158  
106 89 100       437 if(UNIVERSAL::isa( $rec, 'Log::Procmail::Abstract')) {
107             # the folder field is required
108 51 100       101 goto READ unless defined $rec->folder;
109 50         173 $rec->{source} = $log->{source};
110             }
111              
112 88         223 return $rec;
113             }
114              
115             sub push {
116 3     3 1 392 my $log = shift;
117 3         6 push @{ $log->{files} }, @_;
  3         13  
118             }
119              
120             sub errors {
121 6     6 1 1246 my $self = shift;
122 6 100       33 return @_ ? $self->{errors} = shift() : $self->{errors};
123             }
124              
125             sub fh {
126 95     95 1 199 my $log = shift;
127 95 100       338 $log->_open_next unless $log->{fh}->opened();
128 95         638 return $log->{fh};
129             }
130              
131             sub select {
132 3     3 1 1053 my $log = shift;
133 3         8 $log->fh(); # make sure the file is correctly opened and select is updated
134 3         14 return $log->{select};
135             }
136              
137             # *internal method*
138             # opens a file or replace the old filehandle by the new one
139             # push() can therefore accept refs to typeglobs, IO::Handle, or filenames
140             sub _open_next {
141 16     16   120 my ( $log ) = @_;
142 16         30 my $file;
143              
144 16 100       55 if ( @{ $log->{files} } ) {
  16         51  
145 13         18 $file = shift @{ $log->{files} };
  13         31  
146 3         16 } else { return 0 };
147              
148 13 100 66     85 if ( ref $file eq 'GLOB' ) {
    100          
149 1         3 $log->{fh} = *$file{IO};
150 1 50       5 carp "Closed filehandle $log->{fh}" unless $log->{fh}->opened;
151             }
152             elsif ( ref $file && $file->isa('IO::Handle') ) {
153 1         3 $log->{fh} = $file;
154             }
155             else {
156 11 50       60 $log->{fh}->open($file) or carp "Can't open $file: $!";
157             }
158 13         631 $log->{source} = $file;
159 13 50       178 $log->{select} = ( grep $^O eq $_, qw(MSWin32 NetWare dos VMS riscos beos) )
160             ? undef
161             : IO::Select->new( $log->{fh} );
162 13         656 1;
163             }
164              
165             sub DESTROY {
166 10     10   2153 my $self = shift;
167 10 100 66     96 if ( $self->{fh} && $self->{fh}->opened ) { $self->{fh}->close }
  9         123  
168             }
169              
170             #
171             # a small class for the abstracts themselves
172             #
173             package Log::Procmail::Abstract;
174              
175 4     4   29 use Carp;
  4         10  
  4         568  
176              
177             sub new {
178 52     52   148 my $class = shift;
179 52         376 return bless {@_}, $class;
180             }
181              
182             for my $attr (qw( from date subject size folder source ) ) {
183 4     4   24 no strict 'refs';
  4         7  
  4         1857  
184             *$attr = sub {
185 408     408   1533 my $self = shift;
186 408 100       1718 @_ ? $self->{$attr} = shift: $self->{$attr};
187             }
188             }
189              
190             sub ymd {
191 3     3   6 my $self = shift;
192 3 50       14 croak("Log::Procmail::Abstract::ymd cannot be used to set the date")
193             if @_;
194 3 50       13 return undef unless defined $self->{date};
195 3         366 $self->{date} =~ /^$DATE$/o;
196 3 100       25 return undef unless $1;
197 2         29 return sprintf( "%04d%02d%02d$3$4$5", $6, $month{$1} + 1, $2 );
198             }
199              
200             1;
201              
202             __END__