File Coverage

blib/lib/IO/Reverse.pm
Criterion Covered Total %
statement 65 67 97.0
branch 14 22 63.6
condition 4 8 50.0
subroutine 10 10 100.0
pod 2 6 33.3
total 95 113 84.0


line stmt bran cond sub pod time code
1              
2             package IO::Reverse;
3              
4 2     2   319163 use v5.14;
  2         9  
5              
6 2     2   13 use warnings;
  2         17  
  2         145  
7 2     2   40 use strict;
  2         4  
  2         185  
8             # the following only used for development
9             #use Data::Dumper;
10             #use lib './lib'; # local Verbose.pm
11             #use Verbose;
12              
13             =head1 NAME
14              
15             IO::Reverse - read a file in reverse
16              
17             =head1 VERSION
18              
19             Version 0.03
20              
21             =cut
22              
23             our $VERSION = '0.03';
24              
25             =head1 SYNOPSIS
26              
27             Read a file from the end of file, line by line
28              
29             create a test file from the Command Line
30              
31             $ for i in $( seq 1 10 )
32             do
33             echo "this is $i"
34             done > t.txt
35              
36              
37             Now a small test script
38              
39             use IO::Reverse;
40              
41             my $f = IO::Reverse->new(
42             {
43             FILENAME => './t.txt'
44             }
45             );
46              
47             while ( my $line = $f->next ) {
48             print "$line";
49             }
50              
51              
52             =cut
53              
54             =head1 METHODS
55              
56             There are only 2 methods: new() and next();
57              
58             =head2 new
59              
60             my $f = IO::Reverse->new(
61             FILENAME => './t.txt'
62             );
63              
64             =head2 next
65              
66             Iterate through the file
67              
68             while ( my $line = $f->next ) {
69             print "$line";
70             }
71              
72              
73             =cut
74              
75              
76             =head1 AUTHOR
77              
78             Jared Still, C<< >>
79              
80             =head1 BUGS
81              
82             Please report any bugs or feature requests to C, or through
83             the web interface at L. I will be notified, and then you'll
84             automatically be notified of progress on your bug as I make changes.
85              
86             =head1 SUPPORT
87              
88             You can find documentation for this module with the perldoc command.
89              
90             perldoc IO::Reverse
91              
92              
93             You can also look for information at:
94              
95             =over 4
96              
97             =item * RT: CPAN's request tracker (report bugs here)
98              
99             L
100              
101             =item * CPAN Ratings
102              
103             L
104              
105             =item * Search CPAN
106              
107             L
108              
109             =back
110              
111             =head1 LICENSE AND COPYRIGHT
112              
113             This software is Copyright (c) 2023 by Jared Still.
114              
115             This is free software, licensed under:
116              
117             The MIT License
118              
119             =cut
120              
121              
122 2     2   14 use Exporter qw(import);
  2         5  
  2         2151  
123             #our @EXPORT = qw();
124             our @ISA=qw(Exporter);
125              
126             sub new {
127 1     1 1 190272 my ($class, $args) = @_;
128              
129 1 50       48 open(my $fh,'<', $args->{FILENAME}) || die "Reverse: could not open file: $args->{FILENAME} - $!\n";
130 1         6 $args->{FH}=$fh;
131 1         10 $args->{F_SIZE} = -s $args->{FILENAME};
132             # offset starts at penultimate character in file
133              
134             # uncomment if Verbose.pm needed again
135             #$args->{verbose} = Verbose->new(
136             #{
137             #VERBOSITY=>$args->{VERBOSITY},
138             #LABELS=>1,
139             #TIMESTAMP=>0,
140             ##HANDLE=>*STDERR
141             #HANDLE=>*STDOUT
142             #}
143             #);
144              
145 1   50     8 $args->{DEBUG} ||= 0;
146 1   50     5 $args->{CHUNKSIZE} ||= 2**20;
147              
148             # extra initial offset to avoid reading EOF
149 1         4 $args->{F_OFFSET} = ($args->{CHUNKSIZE}+1) * -1; # offset continually decrements to allow reverse seek
150              
151 1 50       5 if ( $args->{CHUNKSIZE} >= abs($args->{F_SIZE}) ) {
152 1         2 $args->{CHUNKSIZE} = $args->{F_SIZE} ;
153 1         3 $args->{F_OFFSET} = ($args->{F_SIZE} * -1) +1 ;
154 1         3 $args->{F_OFFSET} = $args->{CHUNKSIZE} * -1;
155             }
156              
157 1   50     6 $args->{BOF} ||= 0; # true/false - have we reached beginning of file - control used in loadBuffer()
158              
159 1         7 seek $args->{FH}, $args->{F_OFFSET} , 2;
160              
161             # do not use getpos - described as 'opaque' in the docs
162             # only useful for passing to setpos
163             #$args->{F_POS} = $args->{FH}->getpos;
164 1         3 $args->{F_POS} = tell($args->{FH});
165 1   50     7 $args->{DEBUG} ||= 0;
166              
167 1         3 my $self = bless $args, $class;
168              
169 1         4 $self->showReadParameters;
170              
171 1         3 return $self;
172             }
173              
174              
175             # closure to preserve buffer across calls
176             {
177              
178             my ($accumulator,@bufLines) = ('',());
179             my ($firstChar) = ('');
180             my %readHash = ();
181              
182             sub showReadParameters {
183 1     1 0 3 my ($self) = @_;
184             # uncomment if Verbose.pm needed again
185             #$self->{verbose}->print(3," fsize: $self->{F_SIZE}",[]);
186             #$self->{verbose}->print(3," chunkSize: $self->{CHUNKSIZE}",[]);
187             #$self->{verbose}->print(3," offset: $self->{F_OFFSET}",[]);
188             }
189              
190             sub setReadParameters {
191 1     1 0 2 my ($self) = @_;
192              
193 1 50       6 if ( abs($self->{F_OFFSET}) + $self->{CHUNKSIZE} > $self->{F_SIZE} ) {
194 1         3 $self->{CHUNKSIZE} = $self->{F_SIZE} - abs($self->{F_OFFSET}) ;#-1;
195 1         3 $self->{F_OFFSET} = ($self->{F_SIZE} * -1) ; #+1;
196             } else {
197 0         0 $self->{F_OFFSET} += ($self->{CHUNKSIZE} * -1);
198             }
199              
200 1         2 return;
201              
202             }
203              
204              
205             sub dataRead {
206 2     2 0 3 my ($self) = @_;
207 2         3 my $buffer='';
208 2         4 my $iter=0;
209              
210 2         2 while(1) {
211              
212 2         7 my $rsz = 0;
213 2 100       6 if ($self->{CHUNKSIZE} > 0) {
214 1         5 seek $self->{FH}, $self->{F_OFFSET} , 2;
215 1         222 $rsz = read($self->{FH}, $buffer, $self->{CHUNKSIZE} );
216             }
217              
218 2 100       7 if ($rsz < 1) {
219 1         6 @bufLines = split(/\n/, $accumulator);
220 1         3 $self->{BOF} = 1;
221 1         2 return 1;
222             }
223            
224 1         146 $accumulator = $buffer . $accumulator;
225            
226 1         7 $self->setReadParameters();
227              
228 1 50       7 last if $buffer =~ /\n/;
229              
230             }
231              
232 1         266 @bufLines = split(/\n/, $accumulator);
233              
234 1         4 $accumulator = shift @bufLines; # possibly partial line
235 1 50       4 if (@bufLines) {
236 1         3 @bufLines = reverse @bufLines; # needs to be in reverse order if more than 1 element
237             }
238              
239 1         2 return 1;
240             }
241              
242             sub loadBuffer {
243 2     2 0 7 my ($self) = @_;
244              
245 2         6 my $r = $self->dataRead();
246              
247 2         4 return $r;
248              
249             }
250              
251             sub next {
252 11     11 1 65 my ($self) = @_;
253              
254 11 100       29 return undef if $self->{BOF};
255            
256 10 100       18 if (! @bufLines ) {
257 2         6 $self->loadBuffer() ;
258             }
259            
260 10 50       15 if (@bufLines) {
261 10         14 my $f = shift @bufLines;
262 10         36 return $f . "\n";
263             } else {
264 0 0         push @bufLines, $accumulator if $accumulator;
265             }
266              
267             }
268              
269             } # end of closure
270              
271              
272             1;
273              
274