File Coverage

blib/lib/File/SortedSeek.pm
Criterion Covered Total %
statement 117 130 90.0
branch 46 70 65.7
condition 20 32 62.5
subroutine 18 23 78.2
pod 14 16 87.5
total 215 271 79.3


line stmt bran cond sub pod time code
1             package File::SortedSeek;
2 10     10   129438 use strict;
  10         26  
  10         372  
3 10     10   52 use warnings;
  10         104  
  10         334  
4 10     10   20520 use Time::Local;
  10         22239  
  10         889  
5             require Exporter;
6              
7 10     10   78 use vars qw( @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION );
  10         38  
  10         21518  
8              
9             @ISA = qw( Exporter );
10             @EXPORT = ();
11             @EXPORT_OK = qw( alphabetic numeric find_time get_between get_last );
12             %EXPORT_TAGS = ( 'all' => \@EXPORT_OK );
13             $VERSION = '0.015';
14              
15             my $descending = 0;
16             my $cuddle = 0;
17             my $line_length = 80;
18             my $error_msg = '';
19             my $silent = 0;
20             my $exact_match = 0;
21             my %months = ( Jan => 0, Feb => 1, Mar => 2, Apr => 3,
22             May => 4, Jun => 5, Jul => 6, Aug => 7,
23             Sep => 8, Oct => 9, Nov => 10,Dec => 11);
24             my $default_rec_sep = qw/\015\012|\015|\012/;
25              
26             # some subs to set optional vars OO style
27 5     5 1 48254 sub set_cuddle { $cuddle = 1 };
28 4     4 1 788 sub set_no_cuddle { $cuddle = 0 };
29 2     2 1 17052 sub set_descending { $descending = 1 };
30 0     0 1 0 sub set_ascending { $descending = 0 };
31 0     0 0 0 sub set_max_tries { }; # legacy method, no effect
32 0     0 0 0 sub set_line_length { }; # legacy method, no effect
33 9     9 1 241 sub set_silent { $silent = 1 };
34 0     0 1 0 sub set_verbose { $silent = 0 };
35 3     3 1 994 sub was_exact { $exact_match };
36 0     0 1 0 sub error { $error_msg; };
37              
38 6456 100   6456   14322 sub _alphabetic_compare { $descending ? $_[1] cmp $_[0] : $_[0] cmp $_[1] }
39              
40             sub alphabetic {
41 15     15 1 21486 local *FILE = shift;
42 15         32 my $string = shift;
43 15         23 my $munge_ref = shift;
44 15         25 $error_msg = '';
45 15         58 _look( *FILE, $string, \&_alphabetic_compare, $munge_ref );
46             }
47              
48 9304 100   9304   25797 sub _numeric_compare { $descending ? $_[1] <=> $_[0] : $_[0] <=> $_[1] }
49              
50             sub numeric {
51 17     17 1 224451 local *FILE = shift;
52 17         32 my $number = shift;
53 17         32 my $munge_ref = shift;
54 17         30 $error_msg = '';
55 17         72 _look( *FILE, $number, \&_numeric_compare, $munge_ref );
56             }
57              
58             sub find_time {
59 9     9 1 191614 local *FILE = shift;
60 9   33     43 my $find = shift || time;
61 9         17 my $not_gmtime = shift;
62 9         153 $error_msg = '';
63 9 100       175 $find = get_epoch_seconds($find,$not_gmtime) unless $find =~ m/^[\d.]+$/;
64 9         148 _look( *FILE, $find, \&_numeric_compare, \&get_epoch_seconds );
65             }
66              
67             sub get_epoch_seconds {
68 759     759 1 1376 my ($line, $not_gmtime) = @_;
69 759 50       1505 return undef unless defined $line;
70 759         1121 my ($wday,$mon,$mday,$hours,$min,$sec,$year);
71             # look for asctime format: Tue May 27 15:45:00 2008
72             # ignore wday token as this is often dropped ie linux kernel messages
73 759 50       8498 if ($line =~ m/(\w{3})\s+(\d{1,2})\s+(\d\d):(\d\d):(\d\d)\s+(\d{4})/ ) {
    0          
    0          
74 759         33098 ($mon,$mday,$hours,$min,$sec,$year) = ($1,$2,$3,$4,$5,$6);
75             }
76             # look for apache time format: [21/May/2008:17:49:39 +1000]
77             # ignore the time offset
78             elsif($line =~ m!\[(\d{1,2})/(\w{3})/(\d{4}):(\d\d):(\d\d):(\d\d)!x ) {
79 0         0 ($mday,$mon,$year,$hours,$min,$sec) = ($1,$2,$3,$4,$5,$6);
80             }
81             # look for straight epochtime data (ie squid log)
82             elsif($line =~ m/^(\d+)/) {
83 0         0 return $1;
84             }
85 759 50       1742 unless ($year) {
86 0         0 $error_msg = "Unable to find time like string in line:\n$line";
87 0 0       0 warn $error_msg unless $silent;
88 0         0 return undef;
89             }
90 759         1743 $mon = $months{$mon}; # convert to numerical months 0 - 11
91 759 50       2540 return $not_gmtime ? timelocal($sec,$min,$hours,$mday,$mon,$year):
92             timegm($sec,$min,$hours,$mday,$mon,$year);
93             }
94              
95             sub get_between {
96 7     7 1 82 local *FILE = shift;
97 7   100     41 my $begin = shift || 0;
98 7   50     31 my $finish = shift || 0;
99 7   33     53 my $rec_sep = shift || $default_rec_sep;
100 7         17 $error_msg = '';
101 7 50       39 ($begin , $finish) = ($finish, $begin) if $begin > $finish;
102 7         14 my $bytes = $finish - $begin;
103 7         34 sysseek FILE, $begin, 0;
104 7         108 my $read = sysread(FILE, my $buffer, $bytes);
105 7 50       28 if ( $read < $bytes ) {
106 0         0 $error_msg = "Short read\nWanted: $bytes Got: $read\n";
107 0 0       0 warn $error_msg unless $silent;
108 0         0 return undef;
109             }
110 7         27 $buffer = substr $buffer, 0, $bytes;
111 7         383 my @lines = split $rec_sep, $buffer;
112 7 50       78 return wantarray ? @lines : [ @lines ];
113             }
114              
115             sub get_last {
116 4     4 1 357383 local *FILE = shift;
117 4         7 my $num_lines = shift;
118 4   33     20 my $rec_sep = shift || $default_rec_sep;
119 4         6 $error_msg = '';
120 4 50       54 my @stat = stat(FILE) or return undef;
121 4         10 my($size,$blksize) = @stat[7,11];
122 4   50     13 $blksize ||= 8192;
123             # grab the first chunk back from eof at block offset
124 4   33     11 my $pos = $size - (($size % $blksize)|| $blksize );
125 4         5 my $file = '';
126 4         7 my ($buf, $lines);
127 4         3 for(;;) {
128 18 50       36 $pos = 0 if $pos < 0;
129 18         63 sysseek(FILE,$pos,0);
130 18 50       138 sysread(FILE, $buf, $blksize) or last; # returns 0 at eof;
131 18         731 $file = $buf.$file;
132 18         7182 my $lines = () = $file =~ m/$rec_sep/g;
133 18 100 100     938 last if $lines > $num_lines or $pos == 0;
134 14         17 $pos -= $blksize;
135             }
136 4         1733 my @file = split /$rec_sep/, $file;
137 4 100       68 if ( $num_lines > @file ) {
138 2         8 $error_msg = "Unable to find $num_lines\n";
139 2 50       7 warn $error_msg unless $silent;
140 2 100       264 return wantarray ? @file : \@file;
141             }
142             else {
143 2         4 $num_lines = $#file - $num_lines + 1;
144 2 100       51 return wantarray ? @file[$num_lines..$#file] : [@file[$num_lines..$#file]];
145             }
146             }
147              
148             # Modified version of Perl Search::Dict's look()
149              
150             sub _look {
151 41     41   72 local *FILE = shift;
152 41         90 my($key,$comp,$xfrm) = @_;
153 41         61 local $_;
154 41 50       149 return undef if not defined $key;
155 41 50       1013 my @stat = stat(FILE) or return undef;
156 41         130 my($size, $blksize) = @stat[7,11];
157 41   50     127 $blksize ||= 8192;
158             # find the right block
159 41         135 my($min, $max) = (0, int($size / $blksize));
160 41         59 my $mid;
161 41         145 while ($max - $min > 1) {
162 109         179 $mid = int(($max + $min) / 2);
163 109 50       1555 seek(FILE, $mid * $blksize, 0) or return undef;
164 109 50       1721 if $mid; # probably a partial line
165 109         259 $_ = ;
166 109 100       275 $_ = $xfrm->($_) if $xfrm;
167 109         1346 chomp;
168 109 100 66     411 (defined($_) && $comp->($_, $key) < 0) ? $min = $mid : $max = $mid;
169             }
170             # find the right line
171 41         64 $min *= $blksize;
172 41 50       387 seek(FILE,$min,0) or return undef;
173 41 100       210 if $min; # probably a partial line
174 41         77 my $prev_min = $min;
175 41         48 for (;;) {
176 15656         18299 $min = tell(FILE);
177 15656 100       32891 defined($_ = ) or last;
178 15651 100       29812 $_ = $xfrm->($_) if $xfrm;
179 15651         43057 chomp;
180 15651         23773 my $cmp = $comp->($_, $key);
181 15651 100       26395 $exact_match = $cmp==0 ? 1 : 0;
182 15651 100 100     62461 if(!$cuddle and $cmp >= 0){
183 31         333 seek(FILE,$min,0);
184 31         258 return $min;
185             }
186 15620 100 100     35081 if($cuddle and $cmp > 0){
187 5         55 seek(FILE,$prev_min,0);
188 5         44 return $prev_min;
189             }
190 15615         18262 $prev_min = $min;
191             }
192 5         70 return undef;
193             }
194              
195             1;
196              
197             __END__