File Coverage

blib/lib/File/ReadBackwards.pm
Criterion Covered Total %
statement 77 79 97.4
branch 18 24 75.0
condition 11 15 73.3
subroutine 11 11 100.0
pod 6 6 100.0
total 123 135 91.1


line stmt bran cond sub pod time code
1             # File::ReadBackwards.pm
2              
3             # Copyright (C) 2000-2021 by Uri Guttman. All rights reserved.
4             # mail bugs, comments and feedback to uri@stemsystems.com
5              
6             package File::ReadBackwards ;
7              
8 2     2   165996 use strict ;
  2         16  
  2         61  
9 2     2   531 use Symbol ;
  2         816  
  2         127  
10 2     2   13 use Fcntl qw( :seek O_RDONLY ) ;
  2         4  
  2         252  
11 2     2   13 use Carp ;
  2         4  
  2         475  
12              
13             our $VERSION = '1.06' ;
14              
15             my $max_read_size = 1 << 13 ;
16              
17             my $default_rec_sep ;
18              
19             BEGIN {
20              
21             # set the default record separator according to this OS
22             # this needs testing and expansion.
23              
24             # look for CR/LF types
25             # then look for CR types
26             # else it's a LF type
27              
28 2 50 33 2   31 if ( $^O =~ /win32/i || $^O =~ /vms/i ) {
    50          
29              
30 0         0 $default_rec_sep = "\015\012" ;
31             }
32             elsif ( $^O =~ /mac/i ) {
33              
34 0         0 $default_rec_sep = "\015" ;
35             }
36             else {
37 2         6 $default_rec_sep = "\012" ;
38             }
39              
40             # the tied interface is exactly the same as the object one, so all we
41             # need to do is to alias the subs with typeglobs
42              
43 2         6 *TIEHANDLE = \&new ;
44 2         6 *READLINE = \&readline ;
45 2         5 *EOF = \&eof ;
46 2         6 *CLOSE = \&close ;
47 2         4 *TELL = \&tell ;
48              
49             # added getline alias for compatibility with IO::Handle
50              
51 2         1427 *getline = \&readline ;
52             }
53              
54              
55             # constructor for File::ReadBackwards
56              
57             sub new {
58              
59 70     70 1 65972 my( $class, $filename, $rec_sep, $sep_is_regex ) = @_ ;
60              
61             # check that we have a filename
62              
63 70 50       254 defined( $filename ) || return ;
64              
65             # see if this file uses the default of a cr/lf separator
66             # those files will get cr/lf converted to \n
67              
68 70   66     310 $rec_sep ||= $default_rec_sep ;
69 70         146 my $is_crlf = $rec_sep eq "\015\012" ;
70              
71             # get a handle and open the file
72              
73 70         233 my $handle = gensym ;
74 70 50       3610 sysopen( $handle, $filename, O_RDONLY ) || return ;
75 70         418 binmode $handle ;
76              
77             # seek to the end of the file and get its size
78              
79 70 50       486 my $seek_pos = sysseek( $handle, 0, SEEK_END ) or return ;
80              
81             # get the size of the first block to read,
82             # either a trailing partial one (the % size) or full sized one (max read size)
83              
84 70   66     311 my $read_size = $seek_pos % $max_read_size || $max_read_size ;
85              
86             # create the object
87              
88 70         736 my $self = bless {
89             'file_name' => $filename,
90             'handle' => $handle,
91             'read_size' => $read_size,
92             'seek_pos' => $seek_pos,
93             'lines' => [],
94             'is_crlf' => $is_crlf,
95             'rec_sep' => $rec_sep,
96             'sep_is_regex' => $sep_is_regex,
97              
98             }, $class ;
99              
100 70         272 return( $self ) ;
101             }
102              
103             # read the previous record from the file
104             #
105             sub readline {
106              
107 41526     41526 1 196220 my( $self, $line_ref ) = @_ ;
108              
109 41526         51462 my $read_buf ;
110              
111             # get the buffer of lines
112              
113 41526         57828 my $lines_ref = $self->{'lines'} ;
114              
115 41526 100       70353 return unless $lines_ref ;
116              
117 41525         51667 while( 1 ) {
118              
119             # see if there is more than 1 line in the buffer
120              
121 41637 100       50142 if ( @{$lines_ref} > 1 ) {
  41637         74437  
122              
123             # we have a complete line so return it
124             # and convert those damned cr/lf lines to \n
125              
126             $lines_ref->[-1] =~ s/\015\012/\n/
127 41441 100       89452 if $self->{'is_crlf'} ;
128              
129 41441         59536 return( pop @{$lines_ref} ) ;
  41441         82121  
130             }
131              
132             # we don't have a complete, so have to read blocks until we do
133              
134 196         350 my $seek_pos = $self->{'seek_pos'} ;
135              
136             # see if we are at the beginning of the file
137              
138 196 100       424 if ( $seek_pos == 0 ) {
139              
140             # the last read never made more lines, so return the last line in the buffer
141             # if no lines left then undef will be returned
142             # and convert those damned cr/lf lines to \n
143              
144             $lines_ref->[-1] =~ s/\015\012/\n/
145 84 100 100     116 if @{$lines_ref} && $self->{'is_crlf'} ;
  84         331  
146              
147 84         141 return( pop @{$lines_ref} ) ;
  84         255  
148             }
149              
150             # we have to read more text so get the handle and the current read size
151              
152 112         208 my $handle = $self->{'handle'} ;
153 112         174 my $read_size = $self->{'read_size'} ;
154              
155             # after the first read, always read the maximum size
156              
157 112         205 $self->{'read_size'} = $max_read_size ;
158              
159             # seek to the beginning of this block and save the new seek position
160              
161 112         204 $seek_pos -= $read_size ;
162 112         969 sysseek( $handle, $seek_pos, SEEK_SET ) ;
163 112         412 $self->{'seek_pos'} = $seek_pos ;
164              
165             # read in the next (previous) block of text
166              
167 112         1151 my $read_cnt = sysread( $handle, $read_buf, $read_size ) ;
168              
169             # prepend the read buffer to the leftover (possibly partial) line
170              
171 112         322 my $text = $read_buf ;
172 112 100       158 $text .= shift @{$lines_ref} if @{$lines_ref} ;
  46         523  
  112         318  
173              
174             # split the buffer into a list of lines
175             # this may want to be $/ but reading files backwards assumes plain text and
176             # newline separators
177              
178 112 50       21083 @{$lines_ref} = ( $self->{'sep_is_regex'} ) ?
  112         910  
179             $text =~ /(.*?$self->{'rec_sep'}|.+)/gs :
180             $text =~ /(.*?\Q$self->{'rec_sep'}\E|.+)/gs ;
181              
182             #print "Lines \n=>", join( "<=\n=>", @{$lines_ref} ), "<=\n" ;
183              
184             }
185             }
186              
187             sub eof {
188              
189 34     34 1 158 my ( $self ) = @_ ;
190              
191 34         62 my $seek_pos = $self->{'seek_pos'} ;
192 34         44 my $lines_count = @{ $self->{'lines'} } ;
  34         71  
193 34   100     179 return( $seek_pos == 0 && $lines_count == 0 ) ;
194             }
195              
196             sub tell {
197 50     50 1 257 my ( $self ) = @_ ;
198              
199 50         80 my $seek_pos = $self->{'seek_pos'} ;
200 50         99 $seek_pos + length(join "", @{ $self->{'lines'} });
  50         457  
201             }
202              
203             sub get_handle {
204 16     16 1 13121 my ( $self ) = @_ ;
205              
206 16         40 my $handle = $self->{handle} ;
207 16         42 seek( $handle, $self->tell, SEEK_SET ) ;
208 16         70 return $handle ;
209             }
210              
211             sub close {
212              
213 69     69 1 22638 my ( $self ) = @_ ;
214              
215 69         185 my $handle = delete( $self->{'handle'} ) ;
216 69         260 delete( $self->{'lines'} ) ;
217              
218 69         1349 CORE::close( $handle ) ;
219             }
220              
221             __END__