File Coverage

blib/lib/File/SortedSeek/PERLANCAR.pm
Criterion Covered Total %
statement 81 155 52.2
branch 22 78 28.2
condition 10 34 29.4
subroutine 13 25 52.0
pod 15 17 88.2
total 141 309 45.6


line stmt bran cond sub pod time code
1             ## no critic : Modules::ProhibitAutomaticExportation
2              
3             package File::SortedSeek::PERLANCAR;
4              
5             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
6             our $DATE = '2021-04-25'; # DATE
7             our $DIST = 'File-SortedSeek-PERLANCAR'; # DIST
8             our $VERSION = '0.015.1'; # VERSION
9              
10 1     1   77034 use strict;
  1         12  
  1         42  
11 1     1   7 use warnings;
  1         1  
  1         29  
12 1     1   573 use Time::Local;
  1         3136  
  1         88  
13             require Exporter;
14              
15 1     1   8 use vars qw( @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION );
  1         2  
  1         1928  
16              
17             @ISA = qw( Exporter );
18             @EXPORT = ();
19             @EXPORT_OK = qw( binsearch alphabetic numeric find_time get_between get_last );
20             %EXPORT_TAGS = ( 'all' => \@EXPORT_OK );
21              
22             my $descending = 0;
23             my $cuddle = 0;
24             my $line_length = 80;
25             my $error_msg = '';
26             my $silent = 0;
27             my $exact_match = 0;
28             my %months = ( Jan => 0, Feb => 1, Mar => 2, Apr => 3,
29             May => 4, Jun => 5, Jul => 6, Aug => 7,
30             Sep => 8, Oct => 9, Nov => 10,Dec => 11);
31             my $default_rec_sep = qw/\015\012|\015|\012/;
32              
33             # some subs to set optional vars OO style
34 2     2 1 5853 sub set_cuddle { $cuddle = 1 };
35 2     2 1 472 sub set_no_cuddle { $cuddle = 0 };
36 0     0 1 0 sub set_descending { $descending = 1 };
37 0     0 1 0 sub set_ascending { $descending = 0 };
38       0 0   sub set_max_tries { }; # legacy method, no effect
39       0 0   sub set_line_length { }; # legacy method, no effect
40 0     0 1 0 sub set_silent { $silent = 1 };
41 0     0 1 0 sub set_verbose { $silent = 0 };
42 31     31 1 20905 sub was_exact { $exact_match };
43 0     0 1 0 sub error { $error_msg; };
44              
45             sub binsearch {
46 10     10 1 9138 local *FILE = shift;
47 10         22 my $string = shift;
48 10         14 my $compare_ref = shift;
49 10         19 my $munge_ref = shift;
50 10         13 my $minoffset = shift;
51 10         17 my $maxoffset = shift;
52 10         16 $error_msg = '';
53 10 50       22 if ($descending) {
54 0     0   0 _look( *FILE, $string, sub { -$compare_ref->(@_) }, $munge_ref, $minoffset, $maxoffset );
  0         0  
55             } else {
56 10         27 _look( *FILE, $string, $compare_ref, $munge_ref, $minoffset, $maxoffset );
57             }
58             }
59              
60 702 50   702   1302 sub _alphabetic_compare { $descending ? $_[1] cmp $_[0] : $_[0] cmp $_[1] }
61              
62             sub alphabetic {
63 52     52 1 36651 local *FILE = shift;
64 52         148 my $string = shift;
65 52         81 my $munge_ref = shift;
66 52         82 my $minoffset = shift;
67 52         83 my $maxoffset = shift;
68 52         86 $error_msg = '';
69 52         153 _look( *FILE, $string, \&_alphabetic_compare, $munge_ref, $minoffset, $maxoffset );
70             }
71              
72 207 50   207   441 sub _numeric_compare { $descending ? $_[1] <=> $_[0] : $_[0] <=> $_[1] }
73              
74             sub numeric {
75 41     41 1 20872 local *FILE = shift;
76 41         78 my $number = shift;
77 41         68 my $munge_ref = shift;
78 41         64 my $minoffset = shift;
79 41         61 my $maxoffset = shift;
80 41         84 $error_msg = '';
81 41         117 _look( *FILE, $number, \&_numeric_compare, $munge_ref, $minoffset, $maxoffset );
82             }
83              
84             sub find_time {
85 0     0 1 0 local *FILE = shift;
86 0   0     0 my $find = shift || time;
87 0         0 my $not_gmtime = shift;
88 0         0 $error_msg = '';
89 0 0       0 $find = get_epoch_seconds($find,$not_gmtime) unless $find =~ m/^[\d.]+$/;
90 0         0 _look( *FILE, $find, \&_numeric_compare, \&get_epoch_seconds );
91             }
92              
93             sub get_epoch_seconds {
94 0     0 1 0 my ($line, $not_gmtime) = @_;
95 0 0       0 return undef unless defined $line;
96 0         0 my ($wday,$mon,$mday,$hours,$min,$sec,$year);
97             # look for asctime format: Tue May 27 15:45:00 2008
98             # ignore wday token as this is often dropped ie linux kernel messages
99 0 0       0 if ($line =~ m/(\w{3})\s+(\d{1,2})\s+(\d\d):(\d\d):(\d\d)\s+(\d{4})/ ) {
    0          
    0          
100 0         0 ($mon,$mday,$hours,$min,$sec,$year) = ($1,$2,$3,$4,$5,$6);
101             }
102             # look for apache time format: [21/May/2008:17:49:39 +1000]
103             # ignore the time offset
104             elsif($line =~ m!\[(\d{1,2})/(\w{3})/(\d{4}):(\d\d):(\d\d):(\d\d)!x ) {
105 0         0 ($mday,$mon,$year,$hours,$min,$sec) = ($1,$2,$3,$4,$5,$6);
106             }
107             # look for straight epochtime data (ie squid log)
108             elsif($line =~ m/^(\d+)/) {
109 0         0 return $1;
110             }
111 0 0       0 unless ($year) {
112 0         0 $error_msg = "Unable to find time like string in line:\n$line";
113 0 0       0 warn $error_msg unless $silent;
114 0         0 return undef;
115             }
116 0         0 $mon = $months{$mon}; # convert to numerical months 0 - 11
117 0 0       0 return $not_gmtime ? timelocal($sec,$min,$hours,$mday,$mon,$year):
118             timegm($sec,$min,$hours,$mday,$mon,$year);
119             }
120              
121             sub get_between {
122 0     0 1 0 local *FILE = shift;
123 0   0     0 my $begin = shift || 0;
124 0   0     0 my $finish = shift || 0;
125 0   0     0 my $rec_sep = shift || $default_rec_sep;
126 0         0 $error_msg = '';
127 0 0       0 ($begin , $finish) = ($finish, $begin) if $begin > $finish;
128 0         0 my $bytes = $finish - $begin;
129 0         0 sysseek FILE, $begin, 0;
130 0         0 my $read = sysread(FILE, my $buffer, $bytes);
131 0 0       0 if ( $read < $bytes ) {
132 0         0 $error_msg = "Short read\nWanted: $bytes Got: $read\n";
133 0 0       0 warn $error_msg unless $silent;
134 0         0 return undef;
135             }
136 0         0 $buffer = substr $buffer, 0, $bytes;
137 0         0 my @lines = split $rec_sep, $buffer;
138 0 0       0 return wantarray ? @lines : [ @lines ];
139             }
140              
141             sub get_last {
142 0     0 1 0 local *FILE = shift;
143 0         0 my $num_lines = shift;
144 0   0     0 my $rec_sep = shift || $default_rec_sep;
145 0         0 $error_msg = '';
146 0 0       0 my @stat = stat(FILE) or return undef;
147 0         0 my($size,$blksize) = @stat[7,11];
148 0   0     0 $blksize ||= 8192;
149             # grab the first chunk back from eof at block offset
150 0   0     0 my $pos = $size - (($size % $blksize)|| $blksize );
151 0         0 my $file = '';
152 0         0 my ($buf, $lines);
153 0         0 for(;;) {
154 0 0       0 $pos = 0 if $pos < 0;
155 0         0 sysseek(FILE,$pos,0);
156 0 0       0 sysread(FILE, $buf, $blksize) or last; # returns 0 at eof;
157 0         0 $file = $buf.$file;
158 0         0 my $lines = () = $file =~ m/$rec_sep/g;
159 0 0 0     0 last if $lines > $num_lines or $pos == 0;
160 0         0 $pos -= $blksize;
161             }
162 0         0 my @file = split /$rec_sep/, $file;
163 0 0       0 if ( $num_lines > @file ) {
164 0         0 $error_msg = "Unable to find $num_lines\n";
165 0 0       0 warn $error_msg unless $silent;
166 0 0       0 return wantarray ? @file : \@file;
167             }
168             else {
169 0         0 $num_lines = $#file - $num_lines + 1;
170 0 0       0 return wantarray ? @file[$num_lines..$#file] : [@file[$num_lines..$#file]];
171             }
172             }
173              
174             # Modified version of Perl Search::Dict's look()
175              
176             sub _look {
177 103     103   175 local *FILE = shift;
178 103         217 my($key,$comp,$xfrm,$minoffset,$maxoffset) = @_;
179 103         183 local $_;
180 103 50       253 return undef if not defined $key;
181 103         210 my($size, $blksize);
182 103 100       250 if (defined $maxoffset) {
183 16         45 $size = $maxoffset;
184             } else {
185 87 50       1306 my @stat = stat(FILE) or return undef;
186 87         303 ($size, $blksize) = @stat[7,11];
187 87         208 $maxoffset = $size-1;
188             }
189 103   100     344 $blksize ||= 8192;
190 103   100     436 $minoffset ||= 0;
191 103         171 $size -= $minoffset;
192              
193             # find the right block
194 103         310 my($min, $max) = (0, int($size / $blksize));
195 103         158 my $mid;
196 103         278 while ($max - $min > 1) {
197 0         0 $mid = int(($max + $min) / 2);
198 0 0       0 seek(FILE, $mid * $blksize + $minoffset, 0) or return undef;
199 0 0       0 if $mid; # probably a partial line
200 0         0 $_ = ;
201 0 0       0 $_ = $xfrm->($_) if $xfrm;
202 0         0 chomp;
203 0 0 0     0 (defined($_) && $comp->($_, $key) < 0) ? $min = $mid : $max = $mid;
204             }
205             # find the right line
206 103         178 $min *= $blksize;
207 103 50       1213 seek(FILE,$min+$minoffset,0) or return undef;
208 103 50       301 if $min; # probably a partial line
209 103         180 my $prev_min = $min;
210 103         147 for (;;) {
211 967         1462 $min = tell(FILE) - $minoffset;
212 967 100       2824 defined($_ = ) or last;
213 964 50       1811 $_ = $xfrm->($_) if $xfrm;
214 964         1750 chomp;
215 964         1591 my $cmp = $comp->($_, $key);
216 964 100       1760 $exact_match = $cmp==0 ? 1 : 0;
217 964 100 100     2631 if(!$cuddle and $cmp >= 0){
218 91         915 seek(FILE,$min+$minoffset,0);
219 91 100       267 if ($min+$minoffset > $maxoffset) {
220 1         3 $exact_match = 0;
221 1         8 return undef;
222             } else {
223 90         479 return $min+$minoffset;
224             }
225             }
226 873 100 100     1682 if($cuddle and $cmp > 0){
227 9         93 seek(FILE,$prev_min+$minoffset,0);
228 9 100       33 if ($prev_min+$minoffset > $maxoffset) {
229 1         2 $exact_match = 0;
230 1         6 return undef;
231             } else {
232 8         44 return $prev_min+$minoffset;
233             }
234             }
235 864         1239 $prev_min = $min;
236             }
237 3         18 return undef;
238             }
239              
240             1;
241             # ABSTRACT: A fork of File::SortedSeek with some more options
242              
243             __END__