File Coverage

blib/lib/ApacheLog/Parser/SkipList.pm
Criterion Covered Total %
statement 30 156 19.2
branch 0 48 0.0
condition 0 8 0.0
subroutine 10 25 40.0
pod 6 6 100.0
total 46 243 18.9


line stmt bran cond sub pod time code
1             package ApacheLog::Parser::SkipList;
2             $VERSION = v0.0.1;
3              
4 1     1   1521 use warnings;
  1         2  
  1         34  
5 1     1   6 use strict;
  1         1  
  1         31  
6 1     1   5 use Carp;
  1         3  
  1         70  
7              
8 1     1   6 use Digest::MD5 ();
  1         2  
  1         15  
9 1     1   6523 use YAML ();
  1         17526  
  1         2033  
10              
11             =head1 NAME
12              
13             ApacheLog::Parser::SkipList - a list of skippable lines
14              
15             =head1 SYNOPSIS
16              
17             my $skipper = ApacheLog::Parser::SkipList->new;
18             my %regexps = $skipper->set_config(\%conf);
19             # you'll typically build %regexps into some condition sub
20              
21             my $sw = $skipper->new_writer($skipfile);
22             my $counter = 0;
23             while(...) {
24             ...
25             $counter++;
26             $some_condition and $sw->skip($counter);
27             }
28              
29             Later, while reading a file with a prepared skiplist:
30              
31             my $skipper = ApacheLog::Parser::SkipList->new;
32             $skipper->set_config(\%conf);
33              
34             my $sr = $skipper->new_reader($skipfile);
35             my $skip = $sr->next_skip;
36             my $counter = 0;
37             while(my $line = <$fh>) {
38             if(++$counter == $skip) {
39             $counter += $sr->skip_lines($fh);
40             $skip = $sr->next_skip;
41             next;
42             }
43            
44             # then do more expensive stuff
45             ...
46             }
47              
48             =cut
49              
50             =head2 new
51              
52             my $skipper = ApacheLog::Parser::SkipList->new;
53              
54             =cut
55              
56             sub new {
57 0     0 1   my $package = shift;
58 0   0       my $class = ref($package) || $package;
59 0           my $self = {};
60 0           bless($self, $class);
61 0           return($self);
62             } # end subroutine new definition
63             ########################################################################
64              
65             =head2 set_config
66              
67             my %regexps = $skipper->set_config(\%conf);
68              
69             =cut
70              
71             sub set_config {
72 0     0 1   my $self = shift;
73 0           my ($conf) = (@_);
74              
75             my $handle = {
76             file => {
77             ext => sub {
78 0     0     my $s = join('|', @_);
79 0           return(qr/\.(?:$s)$/);
80             },
81             path => sub {
82 0     0     my $s = join('|', @_);
83 0           return(qr/^(?:$s)/);
84             },
85             },
86 0           };
87 0           my %reg;
88 0           foreach my $k (keys(%$conf)) {
89 0           my $ref = $handle->{$k};
90 0           my @ans;
91 0           foreach my $bit (sort({$b cmp $a} keys(%{$conf->{$k}}))) {
  0            
  0            
92 0 0         $ref->{$bit} or croak("no handler for $k/$bit config");
93 0           my $list = $conf->{$k}{$bit};
94 0           push(@ans, $ref->{$bit}->(@$list));
95             }
96 0 0         if(@ans) {
97 0           my $s = join("|", @ans);
98 0           $reg{$k} = qr/(?:$s)/;
99             }
100             }
101              
102 0           $self->{config} = Digest::MD5::md5_hex(YAML::Dump($conf));
103 0           $self->{regexps} = \%reg;
104 0           return(%reg);
105             } # end subroutine set_config definition
106             ########################################################################
107              
108             =head2 get_matcher
109              
110             my $subref = $skipper->get_matcher;
111              
112             =cut
113              
114             sub get_matcher {
115 0     0 1   my $self = shift;
116              
117 0           my %re = %{$self->{regexps}};
  0            
118              
119 0           my $code = '';
120 0           foreach my $type (qw(file)) {
121 0 0         $re{$type} or next;
122 0           $code .= "(\$v->[$type] =~ m#$re{$type}#) and return(1);";
123             }
124             #die "compiling $code";
125 0           my $doskip = eval("
126             use ApacheLog::Parser qw(:fields);
127             my \$code = sub {my \$v = shift; $code};
128             no ApacheLog::Parser;
129             \$code");
130 0 0         $@ and die "cannot build doskip sub -- $@";
131 0           return($doskip);
132             } # end subroutine get_matcher definition
133             ########################################################################
134              
135             =head2 merge
136              
137             Merge existing files (adjusting the offsets.)
138              
139             $skipper->merge($dest, $file, $offset, $file);
140              
141             =cut
142              
143 1     1   12 use constant flag => 2**31;
  1         2  
  1         940  
144              
145             sub merge {
146 0     0 1   my $self = shift;
147 0           my ($dest, @parts) = @_;
148              
149 0           my $outfh = $self->_open_write($dest);
150              
151             # just slurp the entire first bit
152 0           my $first_part = shift(@parts);
153             {
154 0           my $fh = $self->_open_read($first_part);
  0            
155 0           print $outfh readline($fh);
156             }
157              
158 0           while(@parts) {
159 0           my ($offset, $part) = (shift(@parts), shift(@parts));
160 0           my $fh = $self->_open_read($part);
161              
162 0           while(not eof($fh)) {
163 0           my $v;
164 0 0         (read($fh, $v, 4) == 4) or die "gah";
165 0           my $n = unpack("N", $v);
166              
167             # if it is flagged, there's another byte
168 0 0         if($n & flag) {
169 0           my $val;
170 0 0         (read($fh, $val, 4) == 4) or die "gah";
171 0           $n &= ~flag; # de-mangle it
172 0           $n += $offset;
173 0           $n |= flag; # re-mangle
174 0           print $outfh pack('N', $n), $val;
175             }
176             else {
177 0           $n += $offset;
178 0           print $outfh pack('N', $n);
179             }
180             }
181             }
182             } # end subroutine merge definition
183             ########################################################################
184              
185             =head2 new_writer
186              
187             my $sw = $skipper->new_writer($skipfile);
188              
189             =cut
190              
191             sub new_writer {
192 0     0 1   my $self = shift;
193 0           my ($filename) = @_;
194              
195 0           my $fh = $self->_open_write($filename);
196 0           my $writer = __PACKAGE__ . '::Writer';
197 0           return($writer->new($fh));
198             } # end subroutine new_writer definition
199             ########################################################################
200             sub _open_write {
201 0     0     my $self = shift;
202 0           my ($filename) = @_;
203              
204 0 0         my $conf_check = $self->{config} or
205             croak("cannot make a writer without a config");
206              
207 0 0         open(my $fh, '>', $filename) or
208             croak("cannot open '$filename' for writing $!");
209              
210 0           print $fh $conf_check;
211 0           return($fh);
212             }
213              
214             =head2 new_reader
215              
216             my $sr = $skipper->new_reader($skipfile);
217              
218             =cut
219              
220             sub new_reader {
221 0     0 1   my $self = shift;
222 0           my ($filename) = @_;
223              
224 0           my $fh = $self->_open_read($filename);
225 0           my $reader = __PACKAGE__ . '::Reader';
226 0           return($reader->new($fh));
227             } # end subroutine new_reader definition
228             ########################################################################
229             sub _open_read {
230 0     0     my $self = shift;
231 0           my ($filename) = @_;
232 0 0         my $conf_check = $self->{config} or
233             croak("cannot make a reader without a config");
234              
235 0 0         open(my $fh, '<', $filename) or
236             croak("cannot open '$filename' for reading $!");
237              
238 0           my $verify;
239 0           my $ok = read($fh, $verify, 32);
240 0 0 0       (($ok||0) == 32) or
    0          
241             croak("read error on $filename ", (defined($ok) ? 'eof' : $!));
242 0 0         ($verify eq $conf_check) or
243             croak("the config has changed since this skiplist was created\n",
244             " '$verify' vs '$conf_check'");
245              
246 0           return($fh);
247             }
248              
249             {
250             package ApacheLog::Parser::SkipList::Base;
251             sub new {
252 0     0     my $package = shift;
253 0           my ($fh) = @_;
254 0   0       my $class = ref($package) || $package;
255 0           my $self = [$fh, 0, -1];
256 0           bless($self, $class);
257 0           return($self);
258             }
259             }
260             {
261             package ApacheLog::Parser::SkipList::Writer;
262 1     1   7 use Carp;
  1         2  
  1         107  
263             our @ISA = qw(ApacheLog::Parser::SkipList::Base);
264 1     1   7 use constant flag => 2**31;
  1         2  
  1         265  
265             sub skip {
266 0     0     my $self = shift;
267 0           my ($l) = @_;
268              
269 0           my $fh = $self->[0];
270 0 0         if($l == $self->[2]+1) { # contiguous
271 0           $self->[2] = $l;
272             }
273             else {
274             # write-out
275 0 0         if(my $num = $self->[1]) {
276 0 0         if(my $span = $self->[2] - $num) {
277 0           $num |= flag;
278 0           print $fh pack('N2', $num, $span);
279             }
280             else {
281 0           print $fh pack('N', $num);
282             }
283             }
284              
285 0           $self->[1] = $self->[2] = $l;
286             }
287             }
288             sub DESTROY {
289 0 0   0     close($_[0]->[0]) or
290             croak("close file failed $!");
291 0           @{$_[0]} = ();
  0            
292             }
293             }
294             {
295             package ApacheLog::Parser::SkipList::Reader;
296 1     1   5 use Carp;
  1         2  
  1         91  
297             our @ISA = qw(ApacheLog::Parser::SkipList::Base);
298 1     1   5 use constant flag => 2**31;
  1         1  
  1         338  
299              
300             # return the next skip value and setup the line counter
301             sub next_skip {
302 0     0     my $self = shift;
303              
304 0           my $fh = $self->[0];
305 0 0         eof($fh) and return(0);
306              
307 0           my $v;
308 0 0         (read($fh, $v, 4) == 4) or die "gah";
309 0           my $n = unpack("N", $v);
310              
311             # if it is flagged, there's another byte
312 0 0         if($n & flag) {
313 0           my $val;
314 0 0         (read($fh, $val, 4) == 4) or die "gah";
315 0           my $more = unpack("N", $val);
316 0           $self->[1] = $more;
317 0           $n &= ~flag; # de-mangle it
318             }
319             else {
320             # a single line
321 0           $self->[1] = 0;
322             }
323 0           return($self->[2] = $n);
324             }
325             sub skip_lines {
326 0     0     my ($self, $fh) = @_;
327 0 0         my $n = $self->[1] or return(0);
328 0           my $q = 0;
329 0 0         while(<$fh>) { (++$q >= $n) and return($n); }
  0            
330 0           croak("eof while skipping");
331             }
332             }
333              
334             =head1 AUTHOR
335              
336             Eric Wilhelm @
337              
338             http://scratchcomputing.com/
339              
340             =head1 BUGS
341              
342             If you found this module on CPAN, please report any bugs or feature
343             requests through the web interface at L. I will be
344             notified, and then you'll automatically be notified of progress on your
345             bug as I make changes.
346              
347             If you pulled this development version from my /svn/, please contact me
348             directly.
349              
350             =head1 COPYRIGHT
351              
352             Copyright (C) 2007 Eric L. Wilhelm, All Rights Reserved.
353              
354             =head1 NO WARRANTY
355              
356             Absolutely, positively NO WARRANTY, neither express or implied, is
357             offered with this software. You use this software at your own risk. In
358             case of loss, no person or entity owes you anything whatsoever. You
359             have been warned.
360              
361             =head1 LICENSE
362              
363             This program is free software; you can redistribute it and/or modify it
364             under the same terms as Perl itself.
365              
366             =cut
367              
368             # vi:ts=2:sw=2:et:sta
369             1;