| 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__ |