File Coverage

blib/lib/File/ReadBackwards.pm
Criterion Covered Total %
statement 80 82 97.5
branch 18 24 75.0
condition 11 15 73.3
subroutine 12 12 100.0
pod 6 6 100.0
total 127 139 91.3


line stmt bran cond sub pod time code
1             # File::ReadBackwards.pm
2              
3             # Copyright (C) 2003 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   122782 use strict ;
  2         6  
  2         114  
9              
10 2     2   13 use vars qw( $VERSION ) ;
  2         5  
  2         222  
11              
12             $VERSION = '1.05' ;
13              
14 2     2   1711 use Symbol ;
  2         2701  
  2         367  
15 2     2   16 use Fcntl qw( :seek O_RDONLY ) ;
  2         3  
  2         584  
16 2     2   13 use Carp ;
  2         11  
  2         648  
17              
18             my $max_read_size = 1 << 13 ;
19              
20             my $default_rec_sep ;
21              
22             BEGIN {
23              
24             # set the default record separator according to this OS
25             # this needs testing and expansion.
26              
27             # look for CR/LF types
28             # then look for CR types
29             # else it's a LF type
30              
31 2 50 33 2   38 if ( $^O =~ /win32/i || $^O =~ /vms/i ) {
    50          
32              
33 0         0 $default_rec_sep = "\015\012" ;
34             }
35             elsif ( $^O =~ /mac/i ) {
36              
37 0         0 $default_rec_sep = "\015" ;
38             }
39             else {
40 2         5 $default_rec_sep = "\012" ;
41             }
42              
43             # the tied interface is exactly the same as the object one, so all we
44             # need to do is to alias the subs with typeglobs
45              
46 2         7 *TIEHANDLE = \&new ;
47 2         17 *READLINE = \&readline ;
48 2         6 *EOF = \&eof ;
49 2         6 *CLOSE = \&close ;
50 2         6 *TELL = \&tell ;
51              
52             # added getline alias for compatibility with IO::Handle
53              
54 2         2424 *getline = \&readline ;
55             }
56              
57              
58             # constructor for File::ReadBackwards
59              
60             sub new {
61              
62 70     70 1 108797 my( $class, $filename, $rec_sep, $sep_is_regex ) = @_ ;
63              
64             # check that we have a filename
65              
66 70 50       280 defined( $filename ) || return ;
67              
68             # see if this file uses the default of a cr/lf separator
69             # those files will get cr/lf converted to \n
70              
71 70   66     343 $rec_sep ||= $default_rec_sep ;
72 70         151 my $is_crlf = $rec_sep eq "\015\012" ;
73              
74             # get a handle and open the file
75              
76 70         264 my $handle = gensym ;
77 70 50       4726 sysopen( $handle, $filename, O_RDONLY ) || return ;
78 70         204 binmode $handle ;
79              
80             # seek to the end of the file and get its size
81              
82 70 50       532 my $seek_pos = sysseek( $handle, 0, SEEK_END ) or return ;
83              
84             # get the size of the first block to read,
85             # either a trailing partial one (the % size) or full sized one (max read size)
86              
87 70   66     258 my $read_size = $seek_pos % $max_read_size || $max_read_size ;
88              
89             # create the object
90              
91 70         769 my $self = bless {
92             'file_name' => $filename,
93             'handle' => $handle,
94             'read_size' => $read_size,
95             'seek_pos' => $seek_pos,
96             'lines' => [],
97             'is_crlf' => $is_crlf,
98             'rec_sep' => $rec_sep,
99             'sep_is_regex' => $sep_is_regex,
100              
101             }, $class ;
102              
103 70         249 return( $self ) ;
104             }
105              
106             # read the previous record from the file
107             #
108             sub readline {
109              
110 41526     41526 1 226349 my( $self, $line_ref ) = @_ ;
111              
112 41526         45664 my $read_buf ;
113              
114             # get the buffer of lines
115              
116 41526         57010 my $lines_ref = $self->{'lines'} ;
117              
118 41526 100       91439 return unless $lines_ref ;
119              
120 41525         52760 while( 1 ) {
121              
122             # see if there is more than 1 line in the buffer
123              
124 41637 100       42645 if ( @{$lines_ref} > 1 ) {
  41637         100210  
125              
126             # we have a complete line so return it
127             # and convert those damned cr/lf lines to \n
128              
129 41441 100       134715 $lines_ref->[-1] =~ s/\015\012/\n/
130             if $self->{'is_crlf'} ;
131              
132 41441         54159 return( pop @{$lines_ref} ) ;
  41441         111991  
133             }
134              
135             # we don't have a complete, so have to read blocks until we do
136              
137 196         327 my $seek_pos = $self->{'seek_pos'} ;
138              
139             # see if we are at the beginning of the file
140              
141 196 100       491 if ( $seek_pos == 0 ) {
142              
143             # the last read never made more lines, so return the last line in the buffer
144             # if no lines left then undef will be returned
145             # and convert those damned cr/lf lines to \n
146              
147 84         668 $lines_ref->[-1] =~ s/\015\012/\n/
148 84 100 100     112 if @{$lines_ref} && $self->{'is_crlf'} ;
149              
150 84         102 return( pop @{$lines_ref} ) ;
  84         283  
151             }
152              
153             # we have to read more text so get the handle and the current read size
154              
155 112         221 my $handle = $self->{'handle'} ;
156 112         186 my $read_size = $self->{'read_size'} ;
157              
158             # after the first read, always read the maximum size
159              
160 112         417 $self->{'read_size'} = $max_read_size ;
161              
162             # seek to the beginning of this block and save the new seek position
163              
164 112         236 $seek_pos -= $read_size ;
165 112         757 sysseek( $handle, $seek_pos, SEEK_SET ) ;
166 112         222 $self->{'seek_pos'} = $seek_pos ;
167              
168             # read in the next (previous) block of text
169              
170 112         1372 my $read_cnt = sysread( $handle, $read_buf, $read_size ) ;
171              
172             # prepend the read buffer to the leftover (possibly partial) line
173              
174 112         547 my $text = $read_buf ;
175 112 100       139 $text .= shift @{$lines_ref} if @{$lines_ref} ;
  46         217  
  112         300  
176              
177             # split the buffer into a list of lines
178             # this may want to be $/ but reading files backwards assumes plain text and
179             # newline separators
180              
181 112 50       38086 @{$lines_ref} = ( $self->{'sep_is_regex'} ) ?
  112         5956  
182             $text =~ /(.*?$self->{'rec_sep'}|.+)/gs :
183             $text =~ /(.*?\Q$self->{'rec_sep'}\E|.+)/gs ;
184              
185             #print "Lines \n=>", join( "<=\n=>", @{$lines_ref} ), "<=\n" ;
186              
187             }
188             }
189              
190             sub eof {
191              
192 34     34 1 165 my ( $self ) = @_ ;
193              
194 34         62 my $seek_pos = $self->{'seek_pos'} ;
195 34         40 my $lines_count = @{ $self->{'lines'} } ;
  34         63  
196 34   100     183 return( $seek_pos == 0 && $lines_count == 0 ) ;
197             }
198              
199             sub tell {
200 50     50 1 195 my ( $self ) = @_ ;
201              
202 50         97 my $seek_pos = $self->{'seek_pos'} ;
203 50         275 $seek_pos + length(join "", @{ $self->{'lines'} });
  50         604  
204             }
205              
206             sub get_handle {
207 16     16 1 12120 my ( $self ) = @_ ;
208              
209 16         35 my $handle = $self->{handle} ;
210 16         48 seek( $handle, $self->tell, SEEK_SET ) ;
211 16         729 return $handle ;
212             }
213              
214             sub close {
215              
216 69     69 1 28473 my ( $self ) = @_ ;
217              
218 69         330 my $handle = delete( $self->{'handle'} ) ;
219 69         313 delete( $self->{'lines'} ) ;
220              
221 69         1730 CORE::close( $handle ) ;
222             }
223              
224             __END__